When should TaskCompletionSource<T> be used?
I mostly use it when only an event based API is available (for example Windows Phone 8 sockets):
public Task<Args> SomeApiWrapper()
{
TaskCompletionSource<Args> tcs = new TaskCompletionSource<Args>();
var obj = new SomeApi();
// will get raised, when the work is done
obj.Done += (args) =>
{
// this will notify the caller
// of the SomeApiWrapper that
// the task just completed
tcs.SetResult(args);
}
// start the work
obj.Do();
return tcs.Task;
}
So it's especially useful when used together with the C#5 async
keyword.
In my experiences, TaskCompletionSource
is great for wrapping old asynchronous patterns to the modern async/await
pattern.
The most beneficial example I can think of is when working with Socket
. It has the old APM and EAP patterns, but not the awaitable Task
methods that TcpListener
and TcpClient
have.
I personally have several issues with the NetworkStream
class and prefer the raw Socket
. Being that I also love the async/await
pattern, I made an extension class SocketExtender
which creates several extension methods for Socket
.
All of these methods make use of TaskCompletionSource<T>
to wrap the asynchronous calls like so:
public static Task<Socket> AcceptAsync(this Socket socket)
{
if (socket == null)
throw new ArgumentNullException("socket");
var tcs = new TaskCompletionSource<Socket>();
socket.BeginAccept(asyncResult =>
{
try
{
var s = asyncResult.AsyncState as Socket;
var client = s.EndAccept(asyncResult);
tcs.SetResult(client);
}
catch (Exception ex)
{
tcs.SetException(ex);
}
}, socket);
return tcs.Task;
}
I pass the socket
into the BeginAccept
methods so that I get a slight performance boost out of the compiler not having to hoist the local parameter.
Then the beauty of it all:
var listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
listener.Bind(new IPEndPoint(IPAddress.Loopback, 2610));
listener.Listen(10);
var client = await listener.AcceptAsync();
To me, a classic scenario for using TaskCompletionSource
is when it's possible that my method won't necessarily have to do a time consuming operation. What it allows us to do is to choose the specific cases where we'd like to use a new thread.
A good example for this is when you use a cache. You can have a GetResourceAsync
method, which looks in the cache for the requested resource and returns at once (without using a new thread, by using TaskCompletionSource
) if the resource was found. Only if the resource wasn't found, we'd like to use a new thread and retrieve it using Task.Run()
.
A code example can be seen here: How to conditionally run a code asynchonously using tasks