Background service/application on windows platform (Windows App SDK) - c#

I wanna make application for windows using .net maui with basic service such as counter which can be still running after I quit application - something similar to foreground service on android. I try to use background task from uwp but it doesntt work, and I dont know it is correct way to make this app?
I base my app using this guideline:
https://learn.microsoft.com/en-us/windows/uwp/launch-resume/guidelines-for-background-tasks
{
public sealed class DemoService : IBackgroundTask,IBackgroundService
{
private BackgroundTaskDeferral backgroundTaskDeferral;
public void Run(IBackgroundTaskInstance taskInstance)
{
// Get a deferral so that the service isn't terminated.
backgroundTaskDeferral = taskInstance.GetDeferral();
// Associate a cancellation handler with the background task.
taskInstance.Canceled += OnCanceled;
}
private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
//
// Indicate that the background task is canceled.
//
if (this.backgroundTaskDeferral != null)
{
// Complete the service deferral.
this.backgroundTaskDeferral.Complete();
}
}
public void RegisterBackgroundTask()
{
}
public void UnRegisterBackgroundTask()
{
foreach (var cur in BackgroundTaskRegistration.AllTasks)
{
cur.Value.Unregister(true);
}
}
public static BackgroundTaskRegistration RegisterBackgroundTask(string taskEntryPoint,
string taskName,
IBackgroundTrigger trigger,
IBackgroundCondition condition)
{
// Check for existing registrations of this background task.
foreach (var cur in BackgroundTaskRegistration.AllTasks)
{
if (cur.Value.Name == taskName)
{
// The task is already registered.
return (BackgroundTaskRegistration)(cur.Value);
}
}
//
// Register the background task.
//
var builder = new BackgroundTaskBuilder();
builder.Name = taskName;
builder.TaskEntryPoint = taskEntryPoint;
builder.SetTrigger(trigger);
if (condition != null)
{
builder.AddCondition(condition);
}
BackgroundTaskRegistration task = builder.Register();
return task;
}
}
using System.Diagnostics.Metrics;
using System.Reflection;
namespace BackgroundServiceUWP;
public partial class MainPage : ContentPage
{
private bool isRunning;
private int count = 0;
public readonly IBackgroundService service;
public MainPage(IBackgroundService service)
{
InitializeComponent();
this.service = service;
}
private async void RunBackgroundTask(object sender, EventArgs e)
{
//start service
service.RegisterBackgroundTask();
isRunning = true;
while (isRunning)
{
count++;
Counter.Text = count.ToString();
await Task.Delay(TimeSpan.FromSeconds(3));
}
}
private void StopBackgroundTask(object sender, EventArgs e)
{
isRunning = false;
count = 0;
Counter.Text = count.ToString();
//stop service
service.UnRegisterBackgroundTask();
}
}
<Label`enter code here`
x:Name="Counter"
FontSize="18"
HorizontalOptions="Center" />
<Button
Text="Start Service"
Clicked="RunBackgroundTask"
HorizontalOptions="Center" />
<Button
Text="Stop Service"
Clicked="StopBackgroundTask"
HorizontalOptions="Center" />
namespace BackgroundServiceUWP
{
public interface IBackgroundService
{
void RegisterBackgroundTask();
void UnRegisterBackgroundTask();
}
}

There is an existed issue about the Service/Background Task in the .net maui on the github.
And according to the comment in it, this is the limit in the WinUI, you can report it to the WinUI 3 on the github.
In addition, you can check this discussion about Windows App SDK need better support for periodic background tasks. There is a workaround about Timer-triggered background task in the WinUI in it.

Related

How can I use I timer in my Blazor server app, to perform an action periodically?

Hello I have a Blazor server app where some PLC variables are read from a remote production machine.
The code part that is connecting to the PLC and reading the datas is in a sourced service (PLCService.cs). The service is injected and called in a razor page.
If I trigger the reading manualy with a button, all the variables are read correctly. So far no problem. But I want that the variables are read every second from the remote machine's PLC. Therefore I have programmed a timer in the codebehind page of my razor page (not in the service), but the timer is not working. (The values are note read even once)
In the razor page there are also the variables that I read from the PLC, but for making it leaner I have just shown the counter value, that should count each second.
In my razor file:
#inject PLCService PLCService
<button #onclick="Read_CNC_Status">Read PLC Data</button>
<l>#counter_timer</l> // Unfortunately the counter value is always "0"
In my razor.cs file:
using System.Timers;
public partial class Read_PLC_Data
{
public int counter_timer=0;
System.Timers.Timer timer1 = new System.Timers.Timer();
public async void Read_CNC_Status()
{
PLCService.Connect_PLC(); // Connection code is in a sourced service
Initialise_Timer1();
}
public void Initialise_Timer1()
{
timer1.Elapsed += new ElapsedEventHandler(OnTimedEvent1);
timer1.Interval = 1000;
timer1.Enabled = true;
counter_timer = 0;
}
public void OnTimedEvent1(object source, ElapsedEventArgs e)
{
PLCService.Read_PLC_Data();
counter_CNC_status += 1; // This counter is not counting !!!
if(counter_timer >= 30)
{
counter_timer = 0;
timer1.Enabled = false;
}
StateHasChanged();
}
}
Try the periodic timer:
private readonly PeriodicTimer _periodicTimer = new(TimeSpan.FromSeconds(5));
public async void OnGet()
{
while(await _periodicTimer.WaitForNextTickAsync())
{
await ConnectPlc();
}
}
UPDATE 1
If you want to go with your approach you should use InvokeAsync(StateHasChanged) because Blazor would not recognize the state change and not refresh the UI
<h3>#_currentCount</h3>
#code {
private int _currentCount;
private System.Timers.Timer _timer;
protected override void OnInitialized()
{
_timer = new();
_timer.Interval = 1000;
_timer.Elapsed += async (object? sender, ElapsedEventArgs e) =>
{
_currentCount++;
await InvokeAsync(StateHasChanged);
};
_timer.Enabled = true;
}
}
Don't forget to invoke the StateHasChanged method
UPDATE 2
If you want to use the periodic timer that i initially suggest, you can use it this way:
First let's assume that you have a class that is responsible for PLC data and implements the INotifyPropertyChanged interface:
public class PlcData : INotifyPropertyChanged
{
private readonly ILogger<PlcData> _logger;
public event PropertyChangedEventHandler? PropertyChanged;
private void NotifyPropertyChanged([CallerMemberName] string propertyName = "")
{
PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
}
private int _counter;
public int Counter
{
get { return _counter; }
set
{
if (_counter != value)
{
_counter = value;
NotifyPropertyChanged(nameof(Counter));
}
}
}
public PlcData(ILogger<PlcData> logger)
{
_logger = logger;
}
public async Task GetFromPlcAsync()
{
_logger.LogInformation("Get new info: {c}", ++Counter);
await Task.CompletedTask;
}
}
Then Create a background service
public class PlcService : BackgroundService
{
private readonly PlcData _plc;
private readonly PeriodicTimer _timer;
public PlcService(PlcData plc)
{
_plc = plc;
_timer = new(TimeSpan.FromSeconds(1));
}
protected override async Task ExecuteAsync(CancellationToken stoppingToken)
{
while(await _timer.WaitForNextTickAsync(stoppingToken)
&& !stoppingToken.IsCancellationRequested)
{
await _plc.GetFromPlcAsync();
}
}
}
In razor page your need to inject the PlcData
#inject PlcData plcData
<h3>#_currentCount</h3>
#code {
private static int _currentCount;
protected override void OnInitialized()
{
plcData.PropertyChanged += OnIncrement;
}
private async void OnIncrement(object? sender, PropertyChangedEventArgs e)
{
_currentCount = plcData.Counter;
await InvokeAsync(() =>
{
StateHasChanged();
});
}
}
Also you need to add services to the container.
builder.Services.AddHostedService<PlcService>();
...
...
builder.Services.AddSingleton<PlcData>();
Here is a typical Program.cs from .net6 Blazor-Server And that's it!
UPDATE 3
Using System.Threading
#page "/"
#using System.Threading;
<h3>#_currentCount</h3>
#code {
private int _currentCount;
protected override void OnInitialized()
{
var timer = new Timer(new TimerCallback(_ =>
{
_currentCount++;
InvokeAsync(() =>
{
StateHasChanged();
});
}), null, 1000, 1000);
}
}
I don't see where you are calling the Read_CNC_Status() method. If you don't call it, then nothing you wrote about the timer is ever executed. You can call it in the component's OnAfterRender function (or in the constructor of your partial class).
protected override void OnAfterRender(bool firstRender)
{
if (firstRender)
{
Read_CNC_Status();
}
base.OnAfterRender(firstRender);
}
I ran the rest of the code and it works.
EDIT: It's not necessary, but I also recommend that you make sure the handler is not called again before the previous execution is finished (this could happen for example if Read_PLC_Data() is slow and takes more than the timer's Interval to complete). To do that, you can set the AutoReset property of your timer to false and manually restart the timer each time at the end of your handler, like this:
public async void Read_CNC_Status()
{
PLCService.Connect_PLC(); // Connection code is in a sourced service
Initialise_Timer1();
}
public void Initialise_Timer1()
{
timer1.Elapsed += new ElapsedEventHandler(OnTimedEvent1);
timer1.Interval = 1000;
timer1.AutoReset = false;
counter_CNC_status = 0;
timer1.Start();
}
public void OnTimedEvent1(object source, ElapsedEventArgs e)
{
try
{
PLCService.Read_PLC_Data();
counter_CNC_status += 1; // This counter is not counting !!!
}
finally
{
if(counter_CNC_status >= 30)
{
counter_CNC_status = 0;
timer1.Enabled = false;
}
else
{
timer1.Start();
}
}
StateHasChanged();
}
The best approach for me is to use QUARTZ.NET

C# WPF program button click run a task until another button click stop or until cancel token valid

I am creating a WPF app where I want to have a global bool im assuming, on the first button click I’ll set this bool to true and I want it to run a task (continuously call an API method) until I click the button again and it stops it. What would be the best way to do this?
private bool running = false;
private async void BtnTrade1_Buy_Click(object sender, RoutedEventArgs e)
{
if (!running)
{
running = true;
}
else
running = false;
if (running)
{
RunningNrunnin(running);
//tradeClient.GetTradeHistory();
}
}
public void RunningNrunnin(bool running)
{
if (running)
{
Task task = new Task(() =>
{
while (running)
{
GetTradeHistory();
Thread.Sleep(2000);
}
});
task.Start();
}
}
Added Below
I would like to call a method over and over until the user creates a cancel request on a thread in the background. I currently had it so I can call a action (a counter) and update the GUI each second but when I try to do this same thing with a method call it executes only once.
// Here is the method I want to call continously until canceled
private async void HistoryTest()
{
cancellationToken = new CancellationTokenSource();
task = Task.Factory.StartNew(async () =>
{
while (true)
{
cancellationToken.Token.ThrowIfCancellationRequested();
await Client2.GetHistory();
await Task.Delay(2000);
}
}, cancellationToken.Token);
}
public async Task GetHistory()
{
try
{
var response = await Client.Service.GetDataAsync
(
ProductType.BtcUsd,
5,
1
);
}
catch(Exception)
{
throw;
}
}
I made a little console test app to test this so I had to change the method signatures (static) and can't use ButtonClick on a console. I simulated the button click by putting as sleep between the programatic "button click".
This might get you started.
private static bool isRunning = false;
private static int clickCounter = 0;
private static int iterationsCounter = 0;
static void Main(string[] args)
{
Console.WriteLine(“Start”);
for(int i = 0; i < 7; i++)
{
BtnTrade1_Buy_Click();
System.Threading.Thread.Sleep(1000);
}
Console.WriteLine(“END”);
}
private static async Task BtnTrade1_Buy_Click()
{
iterationsCounter = 0;
isRunning = !isRunning;
Console.WriteLine($"Ha: {isRunning} {clickCounter++}");
await RunningNrunnin();
}
private static async Task RunningNrunnin()
{
await Task.Run(() => Runit());
}
private static void Runit()
{
while (isRunning)
{
GetTradeHistory();
System.Threading.Thread.Sleep(100);
}
}
private static void GetTradeHistory()
{
Console.WriteLine($"Hello Test {iterationsCounter++}");
}
Of course you wouldn't need all the counters and the Console.WriteLine() stuff. They are there to allow you to visualize what is happening.
Let me know if you need more info.
You don't need to do anything else inside the BtnTrade1_Buy_Click event handler, beyond toggling the isRunning field:
private bool _isRunning;
private void BtnTrade1_Buy_Click(object sender, RoutedEventArgs e)
{
_isRunning = !_isRunning;
}
The Task that is getting the trade history in a loop, needs to be started only once. You could start it in the Window_Loaded event. Storing the Task in a private field is a good idea, in case you decide to await it at some point, but if you are handling the exceptions inside the task it's not necessary.
private void Window_Loaded(object sender, RoutedEventArgs e)
{
_ = StartTradeHistoryLoopAsync(); // Fire and forget
}
private async Task StartTradeHistoryLoopAsync()
{
while (true)
{
var delayTask = Task.Delay(2000);
if (_isRunning)
{
try
{
await Task.Run(() => GetTradeHistory()); // Run in the ThreadPool
//GetTradeHistory(); // Alternative: Run in the UI thread
}
catch (Exception ex)
{
// Handle the exception
}
}
await delayTask;
}
}
Don't forget to stop the task when the window is closed.
private void Window_Closed(object sender, EventArgs e)
{
_isRunning = false;
}
This will stop the calls to GetTradeHistory(), but will not stop the loop. You may need to add one more private bool field to control the loop itself:
while (_alive) // Instead of while (true)

DeadLock on task.Wait() with Task which edit UI

I'm trying to find some solutions to my problem here, but with no result (or I just do not get them right) so if anyone could help / explain i will be really gratefull.
I'm just developing a tool for system administrators using Win Form and now I need to create a continuous ping on the selected machine which is running on the background. There is an indicator for Online status on UI which I need to edit with background ping. So right now I'm in this state:
Class A (Win form):
ClassB activeRelation = new ClassB();
public void UpdateOnline(Relation pingedRelation)
{
//There is many Relations at one time, but form shows Info only for one...
if (activeRelation == pingedRelation)
{
if (p_Online.InvokeRequired)
{
p_Online.Invoke(new Action(() =>
p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure
));
}
else
{
p_Online.BackgroundImage = (pingedRelation.Online) ? Properties.Resources.Success : Properties.Resources.Failure;
}
}
}
//Button for tunring On/Off the background ping for current machine
private void Btn_PingOnOff_Click(object sender, EventArgs e)
{
Button btn = (sender is Button) ? sender as Button : null;
if (btn != null)
{
if (activeRelation.PingRunning)
{
activeRelation.StopPing();
btn.Image = Properties.Resources.Switch_Off;
}
else
{
activeRelation.StartPing(UpdateOnline);
btn.Image = Properties.Resources.Switch_On;
}
}
}
Class B (class thats represent relation to some machine)
private ClassC pinger;
public void StartPing(Action<Relation> action)
{
pinger = new ClassC(this);
pinger.PingStatusUpdate += action;
pinger.Start();
}
public void StopPing()
{
if (pinger != null)
{
pinger.Stop();
pinger = null;
}
}
Class C (background ping class)
private bool running = false;
private ClassB classb;
private Task ping;
private CancellationTokenSource tokenSource;
public event Action<ClassB> PingStatusUpdate;
public ClassC(ClassB classB)
{
this.classB = classB;
}
public void Start()
{
tokenSource = new CancellationTokenSource();
CancellationToken token = tokenSource.Token;
ping = PingAction(token);
running = true;
}
public void Stop()
{
if (running)
{
tokenSource.Cancel();
ping.Wait(); //And there is a problem -> DeadLock
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
private async Task PingAction(CancellationToken ct)
{
bool previousResult = RemoteTasks.Ping(classB.Name);
PingStatusUpdate?.Invoke(classB);
while (!ct.IsCancellationRequested)
{
await Task.Delay(pingInterval);
bool newResult = RemoteTasks.Ping(classB.Name);
if (newResult != previousResult)
{
previousResult = newResult;
PingStatusUpdate?.Invoke(classB);
}
}
}
So the problem is in deadlock when I cancel token and Wait() for task to complete -> it's still running, but While(...) in task is finished right.
You have a deadlock because ping.Wait(); blocks UI thread.
You should wait for task asynchronously using await.
So, if Stop() is event handler then change it to:
public async void Stop() // async added here
{
if (running)
{
tokenSource.Cancel();
await ping; // await here
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
If it is not:
public async Task Stop() // async added here, void changed to Task
{
if (running)
{
tokenSource.Cancel();
await ping; // await here
ping.Dispose();
tokenSource.Dispose();
}
running = false;
}
As mentioned by #JohnB async methods should have Async suffix so, the method should be named as StopAsync().
Similar problem and solution are explained here - Do Not Block On Async Code
You should avoid synchronous waiting on tasks, so you should always use await with tasks instead of Wait() or Result. Also, as pointed by #Fildor you should use async-await all the way to avoid such situations.

android Bound Service gets frozen when screen is off

I created a very basic Service for dictionary playback using TTS (see the complete code below) and running into the same issue on all my 3 android devices (android versions 5, 7 and 8).
Gist: The app plays vocabulary entries, definitions and examples. Between each of them the app takes pause.
Symptoms:
The issue is happening mostly when I use 8 seconds for pause and the app is in the background mode (the screen is turned off). The playback simply gets frozen.
Sometimes the playback continues on its own with screen turned off after a lengthy pause sometimes being up to 20 - 30 minutes or even longer (but then the next entry is played after a very lenghty pause too, provided that we haven't activated screen). Could be some other process partly waking the phone?
Also, playback continues straight after I pressed Power button and screen turns on.
Debug info:
I was reckoning to press pause in Visual Studio after the app got frozen in order to see which bit of code is the cause - unfortunately the debugger seems to keep the device awake and this issue is extremely difficult to reveal.
In order to prevent my app from being frozen I acquire Partial WakeLock in my service (but this still doesn't help, even though app manifest contains permission for WAKE_LOCK)
private void AcquireWakeLock(MainActivity activity)
{
var mgr = (PowerManager)activity.ApplicationContext.GetSystemService(Context.PowerService);
WakeLock = mgr.NewWakeLock(WakeLockFlags.Partial, "myWakeLock");
WakeLock.Acquire();
}
My app also has Play/Pause button and I use TaskCompletionSource for the app to wait until I resume playback
public async Task PlayPause(bool isChecked, MainActivity mainActivity)
{
if (isChecked)
{
ReleaseWakeLock();
AppSuspended = new TaskCompletionSource<bool>();
Tts.Stop();
}
else
{
AcquireWakeLock(mainActivity);
AppSuspended.TrySetResult(true);
}
}
Then, before each next word/phrase is about to be played I use the following code for my app to wait for my resuming playback
await AppSuspended.Task;
Complete code
[Service(Name = "com.my_app.service.PlaybackService")]
public class PlaybackService : Service, TextToSpeech.IOnInitListener, TextToSpeech.IOnUtteranceCompletedListener
{
public IBinder Binder { get; private set; }
private Java.Util.Locale Lang;
private bool Playing;
private int EntryIndex;
private int DefinitionIndex;
private DictionaryDto Dictionary;
private EntryDto CurrentEntry;
private DefinitionDto CurrentDefinition;
private TaskCompletionSource<bool> AppSuspended;
protected TextToSpeech Tts;
private TaskCompletionSource<bool> PlaybackFinished;
private WakeLock WakeLock;
public override void OnCreate()
{
base.OnCreate();
Tts = new TextToSpeech(this, this);
Lang = Tts.DefaultLanguage;
AppSuspended = new TaskCompletionSource<bool>();
AppSuspended.TrySetResult(true);
}
public override IBinder OnBind(Intent intent)
{
Binder = new PlaybackBinder(this);
return Binder;
}
public override bool OnUnbind(Intent intent)
{
return base.OnUnbind(intent);
}
public override void OnDestroy()
{
Binder = null;
base.OnDestroy();
}
void TextToSpeech.IOnUtteranceCompletedListener.OnUtteranceCompleted(string utteranceId)
{
if (utteranceId.Equals("PlaybackFinished")) { PlaybackFinished.TrySetResult(true); }
}
void TextToSpeech.IOnInitListener.OnInit(OperationResult status)
{
// if we get an error, default to the default language
if (status == OperationResult.Error)
Tts.SetLanguage(Java.Util.Locale.Default);
// if the listener is ok, set the lang
if (status == OperationResult.Success)
{
Tts.SetLanguage(Lang);
Tts.SetOnUtteranceCompletedListener(this);
}
}
public async Task Play(string text)
{
Dictionary<string, string> myHashRender = new Dictionary<string, string>();
myHashRender.Add(TextToSpeech.Engine.KeyParamUtteranceId, "PlaybackFinished");
PlaybackFinished = new TaskCompletionSource<bool>();
Tts.Speak(text, QueueMode.Flush, myHashRender);
await PlaybackFinished.Task;
}
public async Task PlaySilence(long ms)
{
Dictionary<string, string> myHashRender = new Dictionary<string, string>();
myHashRender.Add(TextToSpeech.Engine.KeyParamUtteranceId, "PlaybackFinished");
PlaybackFinished = new TaskCompletionSource<bool>();
Tts.PlaySilence(ms, QueueMode.Flush, myHashRender);
await PlaybackFinished.Task;
}
private async Task PlayDictionary(MainActivity activity)
{
EntryIndex = 0;
for (; EntryIndex < Dictionary.Entries.Count;)
{
CurrentEntry = Dictionary.Entries.ElementAt(EntryIndex);
await AppSuspended.Task;
if (!Playing) { return; }
if (!string.IsNullOrEmpty(CurrentEntry.Text))
{
await AppSuspended.Task;
if (!Playing) { return; }
await Play(CurrentEntry.Text);
}
DefinitionIndex = 0;
for (; DefinitionIndex < CurrentEntry.Definitions.Count();)
{
CurrentDefinition = CurrentEntry.Definitions.ElementAt(DefinitionIndex);
await PlayDefinition();
await PlayExamples();
DefinitionIndex++;
}
if (Playing)
{
DefinitionIndex++;
}
EntryIndex++;
}
}
private async Task PlayExamples()
{
if (!Playing) { return; }
foreach (var example in CurrentDefinition.Examples)
{
if (!string.IsNullOrEmpty(example))
{
await AppSuspended.Task;
if (!Playing) { return; }
await Play(example);
if (Playing)
{
await PlaySilence((long)TimeSpan.FromSeconds(8).TotalMilliseconds);
}
}
}
}
private async Task PlayDefinition()
{
if (!Playing) { return; }
if (!string.IsNullOrEmpty(CurrentEntry.Definitions.ElementAt(DefinitionIndex).Text))
{
await AppSuspended.Task;
if (!Playing) { return; }
await PlayDefinitionText();
if (Playing)
{
await PlaySilence((long)TimeSpan.FromSeconds(7).TotalMilliseconds);
}
}
}
private async Task PlayDefinitionText()
{
await AppSuspended.Task;
await Play($"{CurrentEntry.Definitions.ElementAt(DefinitionIndex).Text}");
}
private void ReleaseWakeLock()
{
if (WakeLock != null)
{
WakeLock.Release();
}
}
private void AcquireWakeLock(MainActivity activity)
{
var mgr = (PowerManager)activity.ApplicationContext.GetSystemService(Context.PowerService);
WakeLock = mgr.NewWakeLock(WakeLockFlags.Partial, "myWakeLock");
WakeLock.Acquire();
}
public async Task PlayPause(bool isChecked, MainActivity mainActivity)
{
if (isChecked)
{
ReleaseWakeLock();
AppSuspended = new TaskCompletionSource<bool>();
Tts.Stop();
}
else
{
AcquireWakeLock(mainActivity);
AppSuspended.TrySetResult(true);
}
}
}
Additional info:
The issue happens on all of my devices
Galaxy C7 (Oreo)
Galaxy Tab A3 (Nougat)
Galaxy A3 (Lollipop)
I investigated the issue thoroughly and followed the recommendation to switch to Foreground Service which solved my problem perfectly.
Tested with Lollipop, Nougat, Oreo.
Foreground Service aproach
Put the following method in your MainActivity class
public void StartForegroundServiceSafely(Intent intent)
{
if (Android.OS.Build.VERSION.SdkInt >= Android.OS.BuildVersionCodes.O)
{
StartForegroundService(intent);
}
else
{
StartService(intent);
}
}
You then start your service via Intent
public void PlayFromFile(Android.Net.Uri uri)
{
AcquireWakeLock();
Intent startIntent = new Intent(this, typeof(PlaybackService));
startIntent.SetAction(PlaybackConsts.Start);
startIntent.PutExtra("uri", uri.ToString());
StartForegroundServiceSafely(startIntent);
}
Implement OnStartCommand method in your service
public class PlaybackService : Service, TextToSpeech.IOnInitListener, TextToSpeech.IOnUtteranceCompletedListener
[return: GeneratedEnum]
public override StartCommandResult OnStartCommand(Intent intent, [GeneratedEnum] StartCommandFlags flags, int startId)
{
if (intent.Action.Equals(PlaybackConsts.Start))
{
var notification =
new Notification.Builder(this)
.SetContentTitle(Resources.GetString(Resource.String.ApplicationName))
.SetContentText("HELLO WORLD")
.SetOngoing(true)
.Build();
StartForeground(SERVICE_RUNNING_NOTIFICATION_ID, notification);
}
if (intent.Action.Equals(PlaybackConsts.Start))
{
var uri = Android.Net.Uri.Parse(intent.GetStringExtra("uri"));
var content = MiscellaneousHelper.GetTextFromStream(ContentResolver.OpenInputStream(uri));
Dictionary = DictionaryFactory.Get(content);
Playing = true;
Task.Factory.StartNew(async () =>
{
await PlayDictionary();
});
}
if (intent.Action.Equals(PlaybackConsts.PlayPause))
{
bool isChecked = intent.GetBooleanExtra("isChecked", false);
PlayPause(isChecked);
}
if (intent.Action.Equals(PlaybackConsts.NextEntry))
{
NextEntry();
}
if (intent.Action.Equals(PlaybackConsts.PrevEntry))
{
PrevEntry();
}
if (intent.Action.Equals(PlaybackConsts.Stop))
{
Task.Factory.StartNew(async () =>
{
await Stop();
});
StopForeground(true);
StopSelf();
}
return StartCommandResult.Sticky;
}
From the code above we've learned how to trigger service's functionality in OnStartCommand method.
How to broadcast events from Service
Define your BroadcastReceiver
[BroadcastReceiver(Enabled = true, Exported = false)]
public class PlaybackBroadcastReceiver : BroadcastReceiver
{
public override void OnReceive(Context context, Intent intent)
{
var activity = MainActivity.GetInstance(); // if you need your activity here, see further code below
if (intent.Action == "renderEntry")
{
string entryHtml = intent.GetStringExtra("html");
// omitting code to keep example concise
}
}
}
Declare receiver field in your MainActivity class.
Also encase you need your activity in BroadcastReceiver class you can declare GetInstance method (singleton approach).
public class MainActivity : AppCompatActivity
{
PlaybackBroadcastReceiver receiver;
protected DrawerLayout drawerLayout;
protected NavigationView navigationView;
protected WakeLock WakeLock;
private static MainActivity instance;
public static MainActivity GetInstance()
{
return instance;
}
protected override void OnCreate(Bundle bundle)
{
base.OnCreate(bundle);
receiver = new PlaybackBroadcastReceiver();
instance = this;
}
protected override void OnStart()
{
base.OnStart();
RegisterReceiver(receiver, new IntentFilter("renderEntry"));
}
In order to unregister receiver use the following line:
UnregisterReceiver(receiver);
Broadcasting events from service
In your service you must also use intent
private void SendRenderEntryBroadcast(EntryDto entry)
{
Intent intent = new Intent("renderEntry");
intent.PutExtra("html", GetEntryHtml(entry));
SendBroadcast(intent);
}

BackgroundMediaPlayer only plays sound while debugging

When my mobile app is suspended I want to play a sound every two seconds.
In OnSuspending I call BackgroundMediaPlayer.Current to initiate the background task.
AudioTask:
public sealed class AudioTask : IBackgroundTask
{
private ThreadPoolTimer timer;
private BackgroundTaskDeferral deferral;
private bool canceled;
public void Run(IBackgroundTaskInstance taskInstance)
{
Debug.WriteLine("AudioTask starting");
deferral = taskInstance.GetDeferral();
StorageFolder folder = await Package.Current.InstalledLocation.GetFolderAsync("Assets");
StorageFile file = await folder.GetFileAsync("sound.wav");
var stream = await file.OpenAsync(FileAccessMode.Read);
BackgroundMediaPlayer.Current.AutoPlay = false;
BackgroundMediaPlayer.Current.SetStreamSource(stream);
timer = ThreadPoolTimer.CreatePeriodicTimer(new TimerElapsedHandler(PeriodicTimerCallback), TimeSpan.FromSeconds(2));
taskInstance.Canceled += new BackgroundTaskCanceledEventHandler(OnCanceled);
}
private void OnCanceled(IBackgroundTaskInstance sender, BackgroundTaskCancellationReason reason)
{
Debug.WriteLine("AudioTask canceled");
canceled = true;
}
private void PeriodicTimerCallback(ThreadPoolTimer timer)
{
Debug.WriteLine("AudioTask timer callback");
if (!canceled)
{
BackgroundMediaPlayer.Current.Play();
}
else
{
timer.Cancel();
deferral.Complete();
}
}
}
This works fine while debugging, but when I use the app without the debugger no sound is being played.

Categories

Resources