Don’t fall for TaskCompletionSource traps
February 9, 2018
Wrapping callback hell with TaskCompletionSource
Ever wanted to turn callback style async code to awaitable form?
You might use TaskCompletionSource
for it.
class Program
{
static void Main(string[] args)
{
Run();
Console.ReadLine();
}
static async Task Run()
{
CallbackStyleAsyncMethod((result) => Console.WriteLine(result)); // callback style
var asyncResult = await CallbackStyleAsyncMethodWrappedAsync(); // async/await style
Console.WriteLine(asyncResult);
}
static Task<string> CallbackStyleAsyncMethodWrappedAsync()
{
var tsc = new TaskCompletionSource<string>();
CallbackStyleAsyncMethod((res) => tsc.SetResult(res));
return tsc.Task;
}
static void CallbackStyleAsyncMethod(Action<string> onFinish)
{
Task.Delay(5000).ContinueWith((_) => onFinish("I'm finished"));
}
}
There is one problem with above code that happens very often.
In case of exception it won’t be catched in try/catch block.
This exception won’t be even printed in console window/terminal unless it’s handled by TaskScheduler.UnobservedTaskException
or DomainUnhandledException
Event.
try
{
var tsc = new TaskCompletionSource<string>();
CallbackStyleAsyncMethod(
(res) => tsc.SetResult(new string[] { "a" }[1]) // runtime exception
);
return tsc.Task;
}
catch (Exception ex)
{
return Task.FromResult(""); // won't be catched
}
Solution is trivial but easy to forget
static Task<string> CallbackStyleAsyncMethodWrappedAsync()
{
try
{
var tsc = new TaskCompletionSource<string>();
CallbackStyleAsyncMethod((res) =>
{
try
{
tsc.SetResult(new string[] { "a" }[1]); // runtime exception
}
catch (Exception ex)
{
tsc.SetException(ex); // exception is handled here
}
});
return tsc.Task;
}
catch (Exception ex)
{
return Task.FromResult(""); // will be catched
}
}
Cheers!