TaskCompletionSource is mainly used for wrapping event based async api with Task without making new Threads.
TaskCompletionSource is just a wrapper for a Task , giving you control over its completion. Thus, a TaskCompletionSource<bool> will contain a Task<bool> , and you can set the bool result based on your own logic.
All members of TaskCompletionSource<TResult> are thread-safe and may be used from multiple threads concurrently.
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
In this blog post, Levi Botelho describes how to use the TaskCompletionSource
to write an asynchronous wrapper for a Process such that you can launch it and await its termination.
public static Task RunProcessAsync(string processPath)
{
var tcs = new TaskCompletionSource<object>();
var process = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo(processPath)
{
RedirectStandardError = true,
UseShellExecute = false
}
};
process.Exited += (sender, args) =>
{
if (process.ExitCode != 0)
{
var errorMessage = process.StandardError.ReadToEnd();
tcs.SetException(new InvalidOperationException("The process did not exit correctly. " +
"The corresponding error message was: " + errorMessage));
}
else
{
tcs.SetResult(null);
}
process.Dispose();
};
process.Start();
return tcs.Task;
}
and its usage
await RunProcessAsync("myexecutable.exe");
It looks like no one mentioned, but I guess unit tests too can be considered real life enough.
I find TaskCompletionSource
to be useful when mocking a dependency with an async method.
In actual program under test:
public interface IEntityFacade
{
Task<Entity> GetByIdAsync(string id);
}
In unit tests:
// set up mock dependency (here with NSubstitute)
TaskCompletionSource<Entity> queryTaskDriver = new TaskCompletionSource<Entity>();
IEntityFacade entityFacade = Substitute.For<IEntityFacade>();
entityFacade.GetByIdAsync(Arg.Any<string>()).Returns(queryTaskDriver.Task);
// later on, in the "Act" phase
private void When_Task_Completes_Successfully()
{
queryTaskDriver.SetResult(someExpectedEntity);
// ...
}
private void When_Task_Gives_Error()
{
queryTaskDriver.SetException(someExpectedException);
// ...
}
After all, this usage of TaskCompletionSource seems another case of "a Task object that does not execute code".
TaskCompletionSource is used to create Task objects that don't execute code. In real world scenarios, TaskCompletionSource is ideal for I/O bound operations. This way, you get all the benefits of tasks (e.g. return values, continuations, etc) without blocking a thread for the duration of the operation. If your "function" is an I/O bound operation, it isn't recommended to block a thread using a new Task. Instead, using TaskCompletionSource, you can create a slave task to just indicate when your I/O bound operation finishes or faults.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With