255

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?

Pang
  • 9,564
  • 146
  • 81
  • 122
Royi Namir
  • 144,742
  • 138
  • 468
  • 792
  • 13
    TaskCompletionSource is mainly used for wrapping event based async api with Task without making new Threads. – Arvis Jun 11 '15 at 13:25

11 Answers11

281

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
GameScripting
  • 16,092
  • 13
  • 59
  • 98
  • 6
    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 Mar 09 '13 at 22:35
  • my question id not tagged as c#5 and i was looking for .net4 usages. – Royi Namir Mar 10 '13 at 09:34
  • It's useful fpr .NET 4 too, you can use the [Task.ContinueWith](http://msdn.microsoft.com/en-us/library/dd270696.aspx) method instead of the `await` keyword from c# 5. – GameScripting Mar 10 '13 at 11:11
  • 7
    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). – Erik Feb 01 '14 at 18:41
  • @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? – Fran_gg7 Jul 28 '15 at 10:38
  • 1
    @Fran_gg7 you could use a CancellationToken, see https://msdn.microsoft.com/en-us/library/dd997396(v=vs.110).aspx or as a new question here on stackoverflow – GameScripting Jul 28 '15 at 14:49
  • 4
    The problem with this implementation is that this generates a memory leak as the event is never released from obj.Done – Walter Verhoeven Apr 29 '18 at 08:02
  • 2
    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. – Adam B Mar 29 '22 at 03:13
  • It's also dangerous to use the default constructor of a `TaskCompletionSource`. It can lead to deadlocks when the `TaskCreationOption` is not set to `RunContinuationsAsynchronously` – Peter Bruins Oct 27 '22 at 08:05
95

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();
Erik
  • 12,730
  • 5
  • 36
  • 42
  • 2
    Why would Task.Factory.StartNew not have worked here? – Tola Odejayi Sep 19 '14 at 07:24
  • 25
    @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. – Frans Bouma Nov 11 '14 at 11:47
  • 4
    Thanks, @Frans-Bouma. So TaskCompletionSource is a handy way of converting code that uses the Begin... End... statements into a task? – Tola Odejayi Nov 12 '14 at 00:56
  • 3
    @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. – Erik Apr 18 '15 at 18:01
  • 4
    Look at the [TaskFactory.FromAsync](https://learn.microsoft.com/en-us/dotnet/api/system.threading.tasks.taskfactory-1.fromasync?view=netframework-4.0) to wrap `Begin.. End...` statements. – MicBig Oct 02 '18 at 07:40
  • You still need TaskCompletionSource when you're managing your own wait handles, and so there is no IAsyncResult with which you can call FromAsync. – naasking May 07 '20 at 15:54
  • As I understood, when you have some callback-heavy methods, you can use TaskCompletionSource to deal with that. If you have a linear, maybe long-running computing method, it's enough to wrap it into Task object – Mike Aug 16 '22 at 08:12
40

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

Community
  • 1
  • 1
Adi Lester
  • 24,731
  • 12
  • 95
  • 110
  • 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. – Royi Namir Mar 10 '13 at 09:35
  • 15
    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`. – Servy Jan 08 '14 at 19:26
  • @Servy `Task.FromResult` is only available since .NET 4.5. Before that, that was the way to achieve this behavior. – Adi Lester Jan 08 '14 at 19:32
  • @AdiLester You're answer is referencing `Task.Run`, indicating it's 4.5+. And my previous comment specifically addressed .NET 4.0. – Servy Jan 08 '14 at 19:34
  • @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. – Adi Lester Jan 08 '14 at 19:38
  • It sounds like you're making things harder than needed. Tasks are (at least since 2011) designed to allow completing work synchronously when possible. http://blogs.msdn.com/b/lucian/archive/2011/04/15/async-ctp-refresh-design-changes.aspx – Glenn Maynard May 25 '14 at 15:47
37

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");
Sarin
  • 1,255
  • 11
  • 14
20

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.

Pang
  • 9,564
  • 146
  • 81
  • 122
v1p3r
  • 677
  • 7
  • 13
19

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".

superjos
  • 12,189
  • 6
  • 89
  • 134
5

There's a real world example with a decent explanation in this post from the "Parallel Programming with .NET" blog. You really should read it, but here's a summary anyway.

The blog post shows two implementations for:

"a factory method for creating “delayed” tasks, ones that won’t actually be scheduled until some user-supplied timeout has occurred."

The first implementation shown is based on Task<> and has two major flaws. The second implementation post goes on to mitigate these by using TaskCompletionSource<>.

Here's that second implementation:

public static Task StartNewDelayed(int millisecondsDelay, Action action)
{
    // Validate arguments
    if (millisecondsDelay < 0)
        throw new ArgumentOutOfRangeException("millisecondsDelay");
    if (action == null) throw new ArgumentNullException("action");

    // Create a trigger used to start the task
    var tcs = new TaskCompletionSource<object>();

    // Start a timer that will trigger it
    var timer = new Timer(
        _ => tcs.SetResult(null), null, millisecondsDelay, Timeout.Infinite);

    // Create and return a task that will be scheduled when the trigger fires.
    return tcs.Task.ContinueWith(_ =>
    {
        timer.Dispose();
        action();
    });
}
urig
  • 16,016
  • 26
  • 115
  • 184
  • would be better to use await on tcs.Task and then use the action() after – Royi Namir Jul 14 '14 at 13:53
  • 5
    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. – Royi Namir Jul 14 '14 at 14:32
  • 3
    Why not just `await Task.Delay(millisecondsDelay); action(); return;` or (in .Net 4.0) `return Task.Delay(millisecondsDelay).ContinueWith( _ => action() );` – sgnsajgon Sep 07 '14 at 21:11
  • @sgnsajgon that would be certainly easier to read and to maintain – JwJosefy Oct 23 '14 at 21:19
  • @JwJosefy Actually, *Task.Delay* method can be implemented by using *TaskCompletionSource*, similarly to above code. The real implementation is here: [Task.cs](http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs) – sgnsajgon Oct 24 '14 at 09:37
4

This may be oversimplifying things but the TaskCompletion source allows one to await on an event. Since the tcs.SetResult is only set once the event occurs, the caller can await on the task.

Watch this video for more insights:

http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Lucian03-TipsForAsyncThreadsAndDatabinding

nmishr
  • 287
  • 3
  • 6
  • 2
    Please place relevant code or documentation here as links can change over time and make this answer irrelevant. – rfornal Apr 02 '15 at 19:22
3

I real world scenario where I have used TaskCompletionSource is when implementing a download queue. In my case if the user starts 100 downloads I don't want to fire them all off at once and so instead of returning a strated task I return a task attached to TaskCompletionSource. Once the download gets completed the thread that is working the queue completes the task.

The key concept here is that I am decoupling when a client asks for a task to be started from when it actually gets started. In this case because I don't want the client to have to deal with resource management.

note that you can use async/await in .net 4 as long as you are using a C# 5 compiler (VS 2012+) see here for more details.

Community
  • 1
  • 1
Yaur
  • 7,333
  • 1
  • 25
  • 36
1

I've used TaskCompletionSource to run a Task until it is cancelled. In this case it's a ServiceBus subscriber that I normally want to run for as long as the application runs.

public async Task RunUntilCancellation(
    CancellationToken cancellationToken,
    Func<Task> onCancel)
{
    var doneReceiving = new TaskCompletionSource<bool>();

    cancellationToken.Register(
        async () =>
        {
            await onCancel();
            doneReceiving.SetResult(true); // Signal to quit message listener
        });

    await doneReceiving.Task.ConfigureAwait(false); // Listen until quit signal is received.
}
Johan Gov
  • 1,262
  • 1
  • 13
  • 26
  • 1
    No need to use 'async' with 'TaskCompletionSource' as it has already created a task – Mandeep Janjua Feb 18 '19 at 01:51
  • 1
    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 – monomo Mar 04 '22 at 12:26
-2

The Blazor's WebAssemblyHost also uses this to prevent .NET VM stop.

await new TaskCompletionSource().Task;
Aloento
  • 99
  • 2
  • 7
  • 1
    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. – Kuro Neko May 19 '22 at 07:57