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.
Related
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.
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)
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.
I would like to add more delay in delay var while the execution waits
Example:
private System.Threading.Tasks.Task delayVar; //Delay var
private async void createDelay() //First function
{
delayVar = System.Threading.Tasks.Task.Delay(milliseconds);
await delayVar;
}
private void addDelay() //Second function
{
delayVar.Milliseconds +=5000;
}
Thanks.
You can't "reset" a Task.Delay, but you can reset a timer which makes it an ideal candidate to solve this problem.
Here's an example:
private System.Threading.Timer timer;
public void Start()
{
timer = new System.Threading.Timer(_ => fireMyCode());
restartTimer();
}
private void onFileChanged(object sender, EventArgs e)
{
restartTimer();
}
private void restartTimer()
{
timer.Change(TimeSpan.FromMinutes(5), TimeSpan.FromMinutes(5));
}
But you don't have to use timers, you can still use Task.Delay with an additional task: the idea is to wait on two tasks, the delay and waiting for the files to change (you can use TaskCompletionSource to "create" a task from an event). If the delay task completes first, fire your code.
Here's an example:
TaskCompletionSource<object> fileChanged = new TaskCompletionSource<object>();
private void onFileChanged(object sender, EventArgs e)
{
fileChanged.TrySetResult(null);
}
private async Task endlessLoop()
{
while (true)
{
await handleFilesNotChanged();
}
}
private async Task handleFilesNotChanged()
{
Task timeout = Task.Delay(TimeSpan.FromMinutes(5));
Task waitForFile = fileChanged.Task;
if (await Task.WhenAny(timeout, waitForFile) == timeout)
{
fireMyCode();
}
fileChanged = new TaskCompletionSource<object>();
}
I am dipping my toes in Tasks (.NET 4.5) and am experiencing increasing handles (in task manager). I have a class doing a simple Play/Stop of audio file using MediaPlayer class (System.Windows.Media namespace). I have a second class which wraps it and exposes sync/async playback.
All is working fine and functionality is OK, but see the number of handles increasing in Task manager which worries me.... Am I doing something wrong here?
Important note: if I comment our the "await Task.Delay(1000);" --> then all is just fine and no leaks are observed... How come??
public partial class Form1 : Form
{
AudioActions syncAction = new AudioActions(#"c:\1.wav", false);
AudioActions asyncAction = new AudioActions(#"c:\1.wav", true);
public Form1()
{
InitializeComponent();
}
private async void syncPlayback(object sender, EventArgs e)
{
for (int i = 0; i < 10; i++)
{
await syncAction.Start();
}
}
private async void asyncPlayback(object sender, EventArgs e)
{
for (int i = 0; i < 100; i++)
{
await asyncAction.Start();
await Task.Delay(100); //remove this line and all is fine!!!!
asyncAction.Stop();
}
Console.WriteLine("done");
}
}
public class AudioActions
{
private Audio audio = null;
private TaskCompletionSource<bool> tcs = null;
private string pathToWaveFile;
private bool async;
public AudioActions(string pathToWaveFile, bool async)
{
this.pathToWaveFile=pathToWaveFile;
this.async=async;
}
public Task Start()
{
tcs = new TaskCompletionSource<bool>();
audio = new Audio();
audio.mediaPlayerPlaybackStoppedEvent += Audio_wmPlaybackStopped;
if (async)
tcs.TrySetResult(true); //since its async operation, lets return immediately and free the task from waiting
audio.PlayAudioFileInMediaPlayer(pathToWaveFile);
return tcs.Task;
}
private void Audio_wmPlaybackStopped()
{
audio.mediaPlayerPlaybackStoppedEvent -= Audio_wmPlaybackStopped;
tcs.TrySetResult(true); //playback stopped. Lets free the task from waiting
}
public void Stop()
{
audio.StopAudioFilePlaybackInMediaPlayer();
}
}
public class Audio
{
MediaPlayer mediaPlayer = null;
public delegate void MediaPlayerPlaybackStoppedDelegate();
public event MediaPlayerPlaybackStoppedDelegate mediaPlayerPlaybackStoppedEvent;
public void PlayAudioFileInMediaPlayer(string pathToWavFile)
{
mediaPlayer = new MediaPlayer();
mediaPlayer.MediaEnded += mediaPlayer_MediaEnded;
mediaPlayer.Open(new Uri(pathToWavFile));
mediaPlayer.Play();
}
void mediaPlayer_MediaEnded(object sender, EventArgs e)
{
mediaPlayerPlaybackStoppedEvent.Invoke();
MediaPlayer mediaPlayer = (MediaPlayer)sender;
mediaPlayer.MediaEnded -= mediaPlayer_MediaEnded;
mediaPlayer.Close();
mediaPlayer = null;
}
public void StopAudioFilePlaybackInMediaPlayer()
{
mediaPlayer.Stop();
mediaPlayer.Close();
mediaPlayer = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
Adding a strip down code:
private async void asyncPlayback(object sender, EventArgs e)
{
AudioActions asyncAction = new AudioActions();
for (int i = 0; i < 100; i++)
{
await asyncAction.Start();
await Task.Delay(1000); //remove this line and all is fine!!!!
asyncAction.Stop();
}
}
public class AudioActions
{
private Audio audio = null;
private TaskCompletionSource<bool> tcs = null;
public Task Start()
{
tcs = new TaskCompletionSource<bool>();
audio = new Audio();
tcs.TrySetResult(true); //since its async operation, lets return immediately and free the task from waiting
audio.PlayAudioFileInMediaPlayer(#"c:\1.wav");
return tcs.Task;
}
public void Stop()
{
audio.StopAudioFilePlaybackInMediaPlayer();
}
}
public class Audio
{
MediaPlayer mediaPlayer = null;
public void PlayAudioFileInMediaPlayer(string pathToWavFile)
{
mediaPlayer = new MediaPlayer();
mediaPlayer.Open(new Uri(pathToWavFile));
mediaPlayer.Play();
}
public void StopAudioFilePlaybackInMediaPlayer()
{
mediaPlayer.Stop();
mediaPlayer.Close();
mediaPlayer = null;
GC.Collect();
GC.WaitForPendingFinalizers();
GC.Collect();
}
}
Modified class solving the problem:
public class Audio
{
public delegate void NaudioPlaybackStoppedDelegate();
public event NaudioPlaybackStoppedDelegate naudioPlaybackStoppedEvent;
private WaveOut player = null;
public void PlayAudioFileUsingNaudio(string pathToWavFile)
{
player = new WaveOut();
AudioFileReader waveFileReader = new AudioFileReader(pathToWavFile);
player.Init(waveFileReader);
player.PlaybackStopped += NAudio_Stopped;
player.Play();
}
private void NAudio_Stopped(object sender, StoppedEventArgs e)
{
player.PlaybackStopped -= NAudio_Stopped;
if (naudioPlaybackStoppedEvent!=null)
naudioPlaybackStoppedEvent.Invoke();
player.Dispose();
}
public void StopAudioFilePlaybackInNaudio()
{
player.Stop();
}
}
Replacing MediaPlayer with NAudio 3rd party solved it.
public class Audio
{
public delegate void NaudioPlaybackStoppedDelegate();
public event NaudioPlaybackStoppedDelegate naudioPlaybackStoppedEvent;
private WaveOut player = null;
public void PlayAudioFileUsingNaudio(string pathToWavFile)
{
player = new WaveOut();
AudioFileReader waveFileReader = new AudioFileReader(pathToWavFile);
player.Init(waveFileReader);
player.PlaybackStopped += NAudio_Stopped;
player.Play();
}
private void NAudio_Stopped(object sender, StoppedEventArgs e)
{
player.PlaybackStopped -= NAudio_Stopped;
if (naudioPlaybackStoppedEvent!=null)
naudioPlaybackStoppedEvent.Invoke();
player.Dispose();
}
public void StopAudioFilePlaybackInNaudio()
{
player.Stop();
}
}