Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

When should TaskCompletionSource<T> be used?

People also ask

Why use TaskCompletionSource?

TaskCompletionSource is mainly used for wrapping event based async api with Task without making new Threads.

How does TaskCompletionSource work?

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.

Is TaskCompletionSource thread safe?

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.