I recently stumbled upon some code which looked something like the following:
var ct = new CancellationToken();
Task.Run(async () => {
while (!ct.IsCancellationRequested)
{
CallToMethodThatMightThrowAnException();
await Task.Delay(1000);
}
});
Notice in the above that the task is not awaited, it is started but not observed in any way. Some would expect that if you wrapped this in a try catch block you would catch an exception:
var ct = new CancellationToken();
try
{
Task.Run(async () => {
while (!ct.IsCancellationRequested)
{
CallToMethodThatMightThrowAnException();
await Task.Delay(1000);
}
});
}
catch (Exception e)
{
//handle...
}
Which might be true if you are running .Net framework 4.0 or lower and the Task is garbage collected. But from 4.5 and above this does not happen unless you await it or observe it using TaskScheduler.UnobservedTaskException (UnobservedTaskException is triggered once the Task is garbage collected).
So what to do if you want to handle exceptions that are thrown from tasks you are not awaiting? It depends on what you want to achieve. If you wish the task to stop if an exception occurs and handle the exception you can use continueWith. Alternatively you might want the Task to continue running forever even though it throws exceptions, you can do this by wrapping the code within the task in a try catch block. I describe both approaches below.
Using continueWith
Below I add a continuation (ContinueWith) handler to my task. This will be triggered once the task completes, if an unhandled exception is thrown within the task it completes as well. When the continuation is called you can check whether the Task has an exception and if so handle it:
var ct = new CancellationToken();
Task.Run(async () => {
while (!ct.IsCancellationRequested)
{
CallToMethodThatMightThrowAnException();
await Task.Delay(1000);
}
}).ContinueWith((t) =>
{
if (t.Exception != null)
t.Exception.Handle((e) => {
//Handle Exception and if OK, return true.
return true;
});
});
This approach is good if you want to handle exceptions and the Task is completed due to an exception (faulted).
Wrapping the Func in a try/catch block with an exception callback
You may also want the thread to continue even though unhandled exceptions occur. One way to do this is to handle the exceptions using a try catch block, as seen below:
var ct = new CancellationToken();
Func<Exception, Task> onException = async (e) =>
{
//TODO handle/log e
await Task.CompletedTask;
};
Task.Run(async () =>
{
while (!ct.IsCancellationRequested)
{
try
{
CallToMethodThatMightThrowAnException();
}
catch (Exception e)
{
await onException(e);
}
finally
{
await Task.Delay(1000);
}
}
});
I have added a "onException" Func which will be called every time there is an exception. This way you can log the exception or handle it in any way you want. The cancellation token still makes it possible to end the Task, but it will no longer stop/complete on exceptions.
That is it!
The two examples I have given in this post can also be combined if needed. I hope you found this blog post useful, let me know in the comments down below if you did. If you have another solution or you ended up doing something better, feel free to comment :)
Note: In the above examples I have removed the pragma CS4014: "Because this call is not awaited, execution of the current method continues before the call is completed" from my above snippets.