Set and forget async tasks
(Banner image from here by GDJ)
Recently I've been using asynchronous C# quite a bit, and I've run into the problem of 'setting and forgetting' an asynchronous task more than once. You might want to do this when handling requests in some sort of server, for example.
I looked into it and came up with a few snippets of code I thought someone else might find useful, so I'm posting them here.
Without further delay, here's the first snippet:
/// <summary>
/// Call this method to allow a given task to complete in the background.
/// Errors will be handled correctly.
/// Useful in fire-and-forget scenarios, like a TCP server for example.
/// From http://stackoverflow.com/a/22864616/1460422
/// Adapted by Starbeamrainbowlabs
/// </summary>
/// <param name="task">The task to forget about.</param>
/// <param name="acceptableExceptions">Acceptable exceptions. Exceptions specified here won't cause a crash.</param>
public static async void ForgetTask(Task task, params Type[] acceptableExceptions)
{
try
{
await task.ConfigureAwait(false);
}
catch (Exception ex)
{
// TODO: consider whether derived types are also acceptable.
if (!acceptableExceptions.Contains(ex.GetType()))
throw;
}
}
All asynchronous methods in C♯ return some form of Task
- and these Task
s can be reconfigured and manipulated to make them run in the background on the thread pool, as in the above. The above also handles exceptions correctly so that your asynchronous methods won't just silently fail.
Talking about exceptions, if you await an asynchronous method, it's highly likely that if they do throw an exception it'll be an AggregateException
. This is not helpful. It doesn't tell us anything about the actual exception that was thrown in the first place! It gets annoying manually inspecting the innerExceptions
property of the AggregateException
very quickly. Thankfully, I've found a solution to that too:
try
{
await DoAsyncWork();
}
catch(AggregateException agError)
{
agError.Handle((error) => {
ExceptionDispatchInfo.Capture(error).Throw();
throw error;
});
}
catch
{
Console.Error.WriteLine("Something went very wrong O.o");
throw;
}
I can't remember where I found the ExceptionDispatchInfo
bit (if it was your idea, please let me know so I can give you appropriate credit!), but the rest I wrote myself. It essentially unwraps the AggregateException
and rethrows each exception in turn, whilst preserving the original stack trace. That way you can track the issue that threw the exception in the first place down.