New Task.WaitAsync(CancellationToken) API and exceptions - c#

I'm trying to use the new .NET 6 Task.WaitAsync(CancellationToken) API.
What I'd like to accomplish is to cancel the waiting for a task, while still being capable of trying to cancel the task itself (it calls an async library and I cannot be sure it will observe the cancellationToken I passed in, or at least not in a timely manner) and avoid to swallow any possible exception it could throw.
So, for example, let's say I want to call an async method:
private async Task DoSomethingAsync(CancellationToken cancellationToken)
{
//do something before the call
await Library.DoSomethingAsync(cancellationToken); // Let's pretend
// this is a call to a libary that accepts a cancellationToken,
// but I cannot be 100% sure it will be observed
}
from a button click event handler:
private async void button1_Click(object sender, EventArgs e)
{
var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var tsk = DoSomethingAsync(cts.Token);
try
{
await tsk.WaitAsync(cts.Token);
}
catch (Exception ex) when (ex is OperationCanceledException)
{
tsk.Forget();
}
}
Now I'm sure the await will last 5 seconds max, but when the OperationCanceledException is caught the task could still be running and I don't want to swallow any of the exceptions that it could throw.
So what can I do now if I don't want to await it?
I thought using a FireAndForget extension method like this inside the catch block:
public static async void Forget(this Task task)
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception)
{
throw;
}
}
Is this an acceptable pattern, or should I just trust the library and hope it will sooner or later be canceled anyway?
And what if it will never do so, will the Forget method await forever?

You could combine the WaitAsync and Forget functionality in a single extension method like the one below:
public async static Task WaitAsyncAndThenOnErrorCrash(this Task task,
CancellationToken cancellationToken)
{
Task waitTask = task.WaitAsync(cancellationToken);
try { await waitTask; }
catch when (waitTask.IsCanceled) { OnErrorCrash(task); throw; }
static async void OnErrorCrash(Task task)
{
try { await task.ConfigureAwait(false); }
catch when (task.IsCanceled) { } // Ignore overdue cancellation
}
}
Usage example:
private async void button1_Click(object sender, EventArgs e)
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
try
{
await DoSomethingAsync(cts.Token).WaitAsyncAndThenOnErrorCrash(cts.Token);
}
catch (OperationCanceledException) { } // Ignore
}
In case the DoSomethingAsync completes with error either before or after the cancellation, the application will crash with a popup saying "Unhandled exception has occurred in your application". The user will have the option to continue running the app, by clicking the "Continue" button:

Related

How to cancel the BLE task when the device gets disconnected?

I'm doing BLE implementation in Xamarin forms. I want to stop the BLE tasks when the device get disconnected. I used cancellation token for stopping the task and also gave a call to a void method which has return as you can see in the following code:
private async Task ConnectForDevice(ScanData scanData, CancellationTokenSource token)
{
try
{
await _adapter.ConnectToDeviceAsync(_device);
}
catch (DeviceConnectionException ex)
{
await UserDialogs.Instance.AlertAsync("Device got disconnected please scan again 2");
disconnected();
token.Cancel();
}
}
public async void disconnected()
{
await Application.Current.MainPage.Navigation.PopAsync();
return;
}
The token.Cancel() should stop the task but it doesn't stop the execution. Instead it goes and executes other async tasks. Is there any way to stop these async task easily? Any suggestion?
You are using the cancellation token wrong and your code does not make a lot of sense.
First you should have a CancellationToken and not a CancellationTokenSource as a parameter in ConnectForDevice if this is the taks you want to cancel. You create a CancellationTokenSource in the class calling ConnectForDevices passen the cancellationsource's token to it, being able to cancel the task from the caller by calling cancellationTaskSource.Cancel().
I don't know the API/Library you are using, but I suppose there is an overload for * _adapter.ConnectToDeviceAsync getting a CancellationToken. There you pass you cancellationToken, which when cancelled will cancel the execution.
This will raise an exception of type TaskCancelledException (if implemented in so in _adapter.ConnectToDevice). I say if implemented, since calling cancellationTokenSource.Cancel() will just mark the token for cancellation, setting IsCancellationRequested=true but somewhere (normally inside the method to be cancelled) token. ThrowIfCancellationRequested() needs to be called or manually checking the state of IsCancellationRequested and throwing or cancelling or whatever, in order to stop the execution.
Also I suppose the ConnectToDeviceAsync method will only connect to the device and return or throw an exception afterwards. You will get no signaling of a disconnection this way. I also suppose the DeviceConnectionException will only raise if an exception when connecting to the device happen, but this does not mean a disconnection, because there was no connection.
Normally there is an event with informs you about this, but can be there is not, so the only way would be polling the connection or sth.
So your code becomes more something like this:
private async Task ConnectForDevice(ScanData scanData, CancellationToken token)
{
try
{
await _adapter.ConnectToDeviceAsync(_device, token);
}
catch (DeviceConnectionException ex)
{
await UserDialogs.Instance.AlertAsync("Device got disconnected please scan again 2");
}
catch (TaskCanceledException tce)
{
Console.WriteLine("Scan was cancelled");
}
finally
{
await Disconnect();
}
}
public Task Disconnect() //NEVER USE ASYNC VOID, ONLY IN EVENT HANDLERS
{
return Application.Current.MainPage.Navigation.PopAsync();
}
Update
You are understanding Plugin.BLE wrong.
Here a basic example (not use in production, is for demo)
void Main()
{
//Create your token source
var tcs = new CancellationTokenSource();
//Since we dont await execution goes on to demo cancellation
ScanAndConnectToKnownDevice(/*{yourDeviceId}*/,tcs.Token);
Thread.Sleep(2000)
tcs.Cancel(); //We cancel the process after 2 seconds.
}
async Task ScanAndConnectToKnownDevice(Guid deviceId, CancellationToken cancellationToken)
{
IDevice device;
//CrossBluetoothLE.Current.Adapter has a few events to handle connection changes
//We wire some of them here
//There is an event for when a device disconnects, which
//will be raised when ANY device disconnects
CrossBluetoothLE.Current.Adapter.DeviceDisconnected += OnDeviceDisconnected;
//There is an event for when a device is discovered in scan
CrossBluetoothLE.Current.Adapter.DeviceDiscovered += OnDeviceDiscovered;
//Start scanning process
try
{
await CrossBluetoothLE.Current.Adapter.StartScanningForDevicesAsync(tcs.Token);
}
catch (TaskCanceledException tce)
{
await UserDialogs.Instance.AlertAsync("Scanning was cancelled");
}
catch (Exception ex)
{
await UserDialogs.Instance.AlertAsync("Error scanning for devices");
}
if (device is null)
{
await UserDialogs.Instance.AlertAsync("Device not found!");
}
else
{
try
{
//This will ONLY CONNECT to the device and then RETURN after connection
//The token you are passing here will cancel THE CONNECTION PROCESS,
//that does not mean device was disconnected, but that there were problems
//CONNECTING to the device
await BluetoothService.ConnectForDevice(device, tcs.Token);
}
catch (DeviceConnectionException ex)
{
await UserDialogs.Instance.AlertAsync("Error connecting to the device!");
}
catch (TaskCanceledException tce)
{
await UserDialogs.Instance.AlertAsync("Connection process was cancelled!");
}
}
}
private async void OnDeviceDisconnected(object sender, DeviceEventArgs e) //Is OK to use async void here
{
if (a.Device.ID == deviceId)
await UserDialogs.Instance.AlertAsync("Device got disconnected please scan again 2");
}
private void OnDeviceDiscovered(object sender, DeviceEventArgs e) //Is OK to use async void here
{
if (a.Device.ID == deviceId)
_device = a.Device;
}

Stuck with async await thread

In constructor I want to call one method type :
private async Task OnLoadPrometDanKorisnikDatum
and I want to wait that method while its finish, and I have more method(3) like this and I want to call this 3 methods in background thread and don't wait him to finish, just want to wait first method. And I want to them executing parallel.
I have methods async Task,and in constructor of view model I call like this
OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat,
DatumVrednost).Wait();
OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja).Wait();
if I don't place .Wait() on the end, program doesn't work. I see in debug mode they run asynchronly, but time spent tell me that they sub(one method time + second method time + ....).
Can someone help me, this is for me very stuf...
Answer
The best way to handle your scenario is to use async void.
I recommend first reading the Explanation section below to fully understand the best practices around async void.
public MyConstructor()
{
ExecuteAsyncMethods();
}
async void ExecuteAsyncMethods()
{
try
{
await OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, DatumVrednost);
await OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja);
}
catch(Exception e)
{
//Handle Exception
}
}
Explanation
Many C# devs are taught "Never use async void", but this is one of the few use-cases for it.
Yes async void can be dangerous and here's why:
Cannot await an async avoid method
Can lead to race conditions
Difficult to catch an Exception thrown by async void methods
E.g. the following try/catch block will not catch the Exception thrown here:
public MyConstructor()
{
try
{
//Cannot await `async void`
AsyncVoidMethodWithException();
}
catch(Exception e)
{
//Will never catch the `Exception` thrown in `AsyncVoidMethodWithException` because `AsyncVoidMethodWithException` cannot be awaited
}
//code here will be executing by the time `AsyncVoidMethodWithException` throws the exception
}
async void AsyncVoidMethodWithException()
{
await Task.Delay(2000);
throw new Exception();
}
That being said, as long as we wrap the contents of our entire async void in a try/catch block, we will be able to catch the exception, like so:
public MyConstructor()
{
AsyncVoidMethodWithException();
}
async void AsyncVoidMethodWithException()
{
try
{
await Task.Delay(2000);
throw new Exception();
}
catch(Exception e)
{
//Exception will be caught and successfully handled
}
}
SafeFireAndForget
I created a library to help with this and its additional benefit is that it avoids writing async void code that could be potentially misused by future devs.
It's open source and also available on NuGet:
Source Code
NuGet Package
SafeFireAndForget
SafeFireAndForget allows us to safely execute a Task whilst not blocking the calling thread and without waiting for it to finish before moving to the next line of code.
Below is a simplified version of SafeFireAndForget that you can add to your project.
However, I recommend copy/pasting its complete source code or adding its NuGet Package to your library to get a more robust implementation
public static async void SafeFireAndForget<TException>(this Task task, Action<TException> onException = null, bool continueOnCapturedContext = false) where TException : Exception
{
try
{
await task.ConfigureAwait(continueOnCapturedContext);
}
catch (TException ex) when (onException != null)
{
onException(ex);
}
}
Using SafeFireAndForget
To use SafeFireAndForget, append it to your method call like so:
OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, DatumVrednost).SafeFireAndForget();
OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja).SafeFireAndForget();
To handle any Exception thrown by that Task, use onException. Here's an example that prints the Exception to the Debug Console:
OnLoadPrometDanKorisnikDatum(KorisnikID, PomocnaDnDDatnaDat, DatumVrednost).SafeFireAndForget(ex => Debug.WriteLine(ex));
OnLoadPrometNedelja(KorisnikID, PomocnaDnDDatnaDatNedelja).SafeFireAndForget(ex => Debug.WriteLine(ex));

Cannot handle exceptions thrown in a task and continue where it left off

I have defined an extension method to capture exceptions thrown in a task and write it to log.
public static Task IgnoreExceptions(this Task task)
{
task.ContinueWith(t =>
{
var ignored = t.Exception;
Console.WriteLine("Error" + ignored.Message);
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted);
return task;
}
However, it doesn't seem to work for me. The execution does not continue where it should be. Can anyone tell what's wrong with my IgnoreExceptions?
try {
await DoSomething().IgnoreExceptions().ConfigureAwait(false);
await DoSomethingElse().IgnoreExceptions().ConfigureAwait(false);
}
catch(Exception e){
// If DoSomething() throws, the error is written to console but the code reaches here instead of continuing to call DoSomethingElse()
}
The problem of your implementation is that you're returning the original task. Return the one that is created by ContinueWith might work (I haven't tested it).
old:
task.ContinueWith(...);
return task;
new:
var result = task.ContinueWith(...);
return result;
But I would prefer an async/await approach and rewrite the extension method like that:
public static async Task IgnoreException(this Task task)
{
try
{
await task;
}
catch (Exception ex)
{
Console.WriteLine($"Error: {ex.Message}");
}
}
DEMO

How Do I Create a Looping Service inside an C# Async/Await application?

I have written a class with a method that runs as a long-running Task in the thread pool. The method is a monitoring service to periodically make a REST request to check on the status of another system. It's just a while() loop with a try()catch() inside so that it can handle its own exceptions and and gracefully continuing if something unexpected happens.
Here's an example:
public void LaunchMonitorThread()
{
Task.Run(() =>
{
while (true)
{
try
{
//Check system status
Thread.Sleep(5000);
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
});
}
It works fine, but I want to know if there's another pattern I could use that would allow the Monitor method to run as regular part of a standard Async/Await application, instead of launching it with Task.Run() -- basically I'm trying to avoid fire-and-forget pattern.
So I tried refactoring the code to this:
public async Task LaunchMonitorThread()
{
while (true)
{
try
{
//Check system status
//Use task.delay instead of thread.sleep:
await Task.Delay(5000);
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
}
But when I try to call the method in another async method, I get the fun compiler warning:
"Because this call is not awaited, execution of the current method continues before the call is completed."
Now I think this is correct and what I want. But I have doubts because I'm new to async/await. Is this code going to run the way I expect or is it going to DEADLOCK or do something else fatal?
What you are really looking for is the use of a Timer. Use the one in the System.Threading namespace. There is no need to use Task or any other variation thereof (for the code sample you have shown).
private System.Threading.Timer timer;
void StartTimer()
{
timer = new System.Threading.Timer(TimerExecution, null, TimeSpan.FromSeconds(5), TimeSpan.FromSeconds(5));
}
void TimerExecution(object state)
{
try
{
//Check system status
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
From the documentation
Provides a mechanism for executing a method on a thread pool thread at specified intervals
You could also use System.Timers.Timer but you might not need it. For a comparison between the 2 Timers see also System.Timers.Timer vs System.Threading.Timer.
If you need fire-and-forget operation, it is fine. I'd suggest to improve it with CancellationToken
public async Task LaunchMonitorThread(CancellationToken token)
{
while (!token.IsCancellationRequested)
{
try
{
//Check system status
//Use task.delay instead of thread.sleep:
await Task.Delay(5000, token);
}
catch (Exception e)
{
Console.WriteLine("An error occurred. Resuming on next loop...");
}
}
}
besides that, you can use it like
var cancellationToken = new CancellationToken();
var monitorTask = LaunchMonitorThread(cancellationToken);
and save task and/or cancellationToken to interrupt monitor wherever you want
The method Task.Run that you use to fire is perfect to start long-running async functions from a non-async method.
You are right: the forget part is not correct. If for instance your process is going to close, it would be neater if you kindly asked the started thread to finish its task.
The proper way to do this would be to use a CancellationTokenSource. If you order the CancellationTokenSource to Cancel, then all procedures that were started using Tokens from this CancellationTokenSource will stop neatly within reasonable time.
So let's create a class LongRunningTask, that will create a long running Task upon construction and Cancel this task using the CancellationTokenSource upon Dispose().
As both the CancellationTokenSource as the Task implement IDisposable the neat way would be to Dispose these two when the LongRunningTask object is disposed
class LongRunningTask : IDisposable
{
public LongRunningTask(Action<CancellationToken> action)
{ // Starts a Task that will perform the action
this.cancellationTokenSource = new CancellationTokenSource();
this.longRunningTask = Task.Run( () => action (this.cancellationTokenSource.Token));
}
private readonly CancellationTokenSource cancellationTokenSource;
private readonly Task longRunningTask;
private bool isDisposed = false;
public async Task CancelAsync()
{ // cancel the task and wait until the task is completed:
if (this.isDisposed) throw new ObjectDisposedException();
this.cancellationTokenSource.Cancel();
await this.longRunningTask;
}
// for completeness a non-async version:
public void Cancel()
{ // cancel the task and wait until the task is completed:
if (this.isDisposed) throw new ObjectDisposedException();
this.cancellationTokenSource.Cancel();
this.longRunningTask.Wait;
}
}
Add a standard Dispose Pattern
public void Dispose()
{
this.Dispose(true);
GC.SuppressFinalize(this);
}
protected void Dispose(bool disposing)
{
if (disposing && !this.isDisposed)
{ // cancel the task, and wait until task completed:
this.Cancel();
this.IsDisposed = true;
}
}
Usage:
var longRunningTask = new LongRunningTask( (token) => MyFunction(token)
...
// when application closes:
await longRunningTask.CancelAsync(); // not necessary but the neat way to do
longRunningTask.Dispose();
The Action {...} has a CancellationToken as input parameter, your function should regularly check it
async Task MyFunction(CancellationToken token)
{
while (!token.IsCancellationrequested)
{
// do what you have to do, make sure to regularly (every second?) check the token
// when calling other tasks: pass the token
await Task.Delay(TimeSpan.FromSeconds(5), token);
}
}
Instead of checking for Token, you could call token.ThrowIfCancellationRequested. This will throw an exception that you'll have to catch

Handle exception thrown by a task

I have a task running a long time operation in WPF:
Task t = Task.Factory.StartNew(() =>
{
try
{
process(cancelTokenSource.Token, CompressionMethod, OpInfo);
}
catch (OperationCanceledException)
{
logger.Info("Operation cancelled by the user");
}
}, cancelTokenSource.Token);
try
{
t.Wait();
}
catch (AggregateException ae)
{
int i = 0;
}
private void process(CancellationToken token, CompressionLevel level, OperationInfo info)
{
// check hash
if (ComputeHash)
{
logger.Info("HASH CHECKING NOT IMPLEMENTED YET!");
MessageBox.Show(this,"HASH CHECKING NOT IMPLEMENTED YET!", "WARNING", MessageBoxButton.OK, MessageBoxImage.Warning);
}
token.ThrowIfCancellationRequested();
UserMsgPhase = "Operation finished";
return info;
}
Problem is "MessageBox.Show" throws an exception and it is not captured within "catch (AggregateException ae)". I've been reading about TPL exception handling but I don't understand why it is not catched. Please, could you help me?
Once the task is complete you can check its Exception property. You also have Status and IsCompleted properties which may be useful to you...
Check Task.Exception.
If your task is typed (returning a result), then accessing myTask.Result will throw this exception.
Moreover, if you are running .Net 4.5, you could use async/await.
As an example:
public async void MyButton_OnClick(object sender, EventArgs e)
{
try
{
Task t = ...your task...;
var myResult = await t; // do whatever you like with your task's result (if any)
}catch
{
// whatever you need
}
}
as you would do with synchronous code (but this is not an actual synchronous call)
I believe that the question's process method is a Task, so it looks like it could be implement in a different manner:
You can make the process to be implemented as Task and then you will have a task-child within task-parent.
Then you can make use of the TaskCreationOptions.AttachedToParent option.
According to Stephen Toub, using AttachedToParent will help notify children-task exception to the parent-task catch:
any exceptions from faulted children will propagate up to the parent
Task (unless the parent Task observes those exceptions before it
completes).
Example:
I've omitted the cancellation token parts in order for it to be more simple.
Task t = Task.Factory.StartNew(() =>
{
var process = new Task(() =>
{
//Copy here the process logic.
}, TaskCreationOptions.AttachedToParent);
//*Private failure handler*.
process.start();
});
try
{
t.Wait();
}
catch (AggregateException ae)
{
//handle exceptions from process.
}
In addition, you may add a private failure handler like:
//*Private failure handler*.
var failHandler = child.ContinueWith(t =>
{
//Oops, something went wrong...
}, TaskContinuationOptions.AttachedToParent|TaskContinuationOptions.OnlyOnFaulted);

Categories