When should TaskCompletionSource<T> be used?

108,567

Solution 1

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.

Solution 2

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();

Solution 3

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

Solution 4

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");

Solution 5

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.

Share:
108,567

Related videos on Youtube

Royi Namir
Author by

Royi Namir

Updated on July 10, 2022

Comments

  • Royi Namir
    Royi Namir almost 2 years

    AFAIK, all it knows is that at some point, its SetResult or SetException method is being called to complete the Task<T> exposed through its Task property.

    In other words, it acts as the producer for a Task<TResult> and its completion.

    I saw here the example:

    If I need a way to execute a Func<T> asynchronously and have a Task<T> to represent that operation.

    public static Task<T> RunAsync<T>(Func<T> function) 
    { 
        if (function == null) throw new ArgumentNullException(“function”); 
        var tcs = new TaskCompletionSource<T>(); 
        ThreadPool.QueueUserWorkItem(_ => 
        { 
            try 
            {  
                T result = function(); 
                tcs.SetResult(result);  
            } 
            catch(Exception exc) { tcs.SetException(exc); } 
        }); 
        return tcs.Task; 
    }
    

    Which could be used if I didn’t have Task.Factory.StartNew - But I do have Task.Factory.StartNew.

    Question:

    Can someone please explain by example a scenario related directly to TaskCompletionSource and not to a hypothetical situation in which I don't have Task.Factory.StartNew?

    • Arvis
      Arvis almost 9 years
      TaskCompletionSource is mainly used for wrapping event based async api with Task without making new Threads.
  • Royi Namir
    Royi Namir about 11 years
    can you write in words what do we see here? is it like that SomeApiWrapper is waited upon somewhere , untill the publisher raise the event which cause this task to complete ?
  • Royi Namir
    Royi Namir about 11 years
    my question id not tagged as c#5 and i was looking for .net4 usages.
  • Royi Namir
    Royi Namir about 11 years
    I did see your question and also the answer. (look at my comment to the answer ) ....:-) and indeed it is an educative question and answer.
  • GameScripting
    GameScripting about 11 years
    It's useful fpr .NET 4 too, you can use the Task.ContinueWith method instead of the await keyword from c# 5.
  • Servy
    Servy over 10 years
    This is actually not a situation in which TCS is needed. You can simply use Task.FromResult to do this. Of course, if you're using 4.0 and don't have a Task.FromResult what you'd use a TCS for is to write your own FromResult.
  • Adi Lester
    Adi Lester over 10 years
    @Servy Task.FromResult is only available since .NET 4.5. Before that, that was the way to achieve this behavior.
  • Servy
    Servy over 10 years
    @AdiLester You're answer is referencing Task.Run, indicating it's 4.5+. And my previous comment specifically addressed .NET 4.0.
  • Adi Lester
    Adi Lester over 10 years
    @Servy Not everyone reading this answer is targeting .NET 4.5+. I believe this is a good and valid answer that helps people asking the OP's question (which by the way is tagged .NET-4.0). Either way, downvoting it seems a bit much to me, but if you really believe it deserves a downvote then go ahead.
  • Erik
    Erik over 10 years
    Just an update, Microsoft has released the Microsoft.Bcl.Async package on NuGet which allows the async/await keywords in .NET 4.0 projects (VS2012 and higher is recommended).
  • Glenn Maynard
    Glenn Maynard almost 10 years
    It sounds like you're making things harder than needed. Tasks are (at least since 2011) designed to allow completing work synchronously when possible. blogs.msdn.com/b/lucian/archive/2011/04/15/…
  • Royi Namir
    Royi Namir almost 10 years
    would be better to use await on tcs.Task and then use the action() after
  • Royi Namir
    Royi Namir almost 10 years
    beucase you're back to the context where you left , where Continuewith doesn't preserver the context. (not by default) also if the next statement in action() causes an exception , it would be hard to catch it where using await will show you as a regular exception.
  • sgnsajgon
    sgnsajgon over 9 years
    Why not just await Task.Delay(millisecondsDelay); action(); return; or (in .Net 4.0) return Task.Delay(millisecondsDelay).ContinueWith( _ => action() );
  • Tola Odejayi
    Tola Odejayi over 9 years
    Why would Task.Factory.StartNew not have worked here?
  • JwJosefy
    JwJosefy over 9 years
    @sgnsajgon that would be certainly easier to read and to maintain
  • sgnsajgon
    sgnsajgon over 9 years
    @JwJosefy Actually, Task.Delay method can be implemented by using TaskCompletionSource, similarly to above code. The real implementation is here: Task.cs
  • Jennifer Miles
    Jennifer Miles over 9 years
    @Tola As that would have created a new task running on a threadpool thread, but the code above utilizes the i/o completion thread started by BeginAccept, i.o.w.: it doesn't start a new thread.
  • Tola Odejayi
    Tola Odejayi over 9 years
    Thanks, @Frans-Bouma. So TaskCompletionSource is a handy way of converting code that uses the Begin... End... statements into a task?
  • rfornal
    rfornal about 9 years
    Please place relevant code or documentation here as links can change over time and make this answer irrelevant.
  • Erik
    Erik about 9 years
    @TolaOdejayi Bit of a late reply, but yes that is one of the primary use cases I have found for it. It works wonderfully for this transition of code.
  • Fran_gg7
    Fran_gg7 almost 9 years
    @GameScripting, if I do await SomeApiWrapper() in a WPF page, but, while it is running the method I navigate to another page which also calls SomeApiWrapper(), how can I stop the first execution?
  • GameScripting
    GameScripting almost 9 years
    @Fran_gg7 you could use a CancellationToken, see msdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspx or as a new question here on stackoverflow
  • Walter Verhoeven
    Walter Verhoeven about 6 years
    The problem with this implementation is that this generates a memory leak as the event is never released from obj.Done
  • MicBig
    MicBig over 5 years
    Look at the TaskFactory<TResult>.FromAsync to wrap Begin.. End... statements.
  • Mandeep Janjua
    Mandeep Janjua about 5 years
    No need to use 'async' with 'TaskCompletionSource' as it has already created a task
  • naasking
    naasking about 4 years
    You still need TaskCompletionSource when you're managing your own wait handles, and so there is no IAsyncResult with which you can call FromAsync.
  • monomo
    monomo about 2 years
    Not that this code uses a hidden async void - if await onCancel() throws an Exception it will be swallowed up by your async void and doneReceiving.SetResult(true) will no execute, thus causing your application to stuck forever on your await in the last line
  • Adam B
    Adam B about 2 years
    This answer isn't as helpful as it could be. It just shows code without any real explanation. I think it could be improved on.
  • Kuro Neko
    Kuro Neko almost 2 years
    Please add more details/explanations in your answer, you can put links/citations to back it up. As you can see there are already 10 answers with high scores, adding other answers without proper explanations can leave your answer being ignored, or worst, deleted.