When should TaskCompletionSource<T> be used?
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.
Related videos on Youtube
Royi Namir
Updated on July 10, 2022Comments
-
Royi Namir almost 2 years
AFAIK, all it knows is that at some point, its
SetResult
orSetException
method is being called to complete theTask<T>
exposed through itsTask
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 aTask<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 haveTask.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 haveTask.Factory.StartNew
?-
Arvis almost 9 yearsTaskCompletionSource is mainly used for wrapping event based async api with Task without making new Threads.
-
-
Royi Namir about 11 yearscan 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 about 11 yearsmy question id not tagged as c#5 and i was looking for .net4 usages.
-
Royi Namir about 11 yearsI 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 about 11 yearsIt's useful fpr .NET 4 too, you can use the Task.ContinueWith method instead of the
await
keyword from c# 5. -
Servy over 10 yearsThis 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 aTask.FromResult
what you'd use a TCS for is to write your ownFromResult
. -
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 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 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 over 10 yearsJust an update, Microsoft has released the
Microsoft.Bcl.Async
package on NuGet which allows theasync/await
keywords in .NET 4.0 projects (VS2012 and higher is recommended). -
Glenn Maynard almost 10 yearsIt 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 almost 10 yearswould be better to use await on tcs.Task and then use the action() after
-
Royi Namir almost 10 yearsbeucase 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 over 9 yearsWhy not just
await Task.Delay(millisecondsDelay); action(); return;
or (in .Net 4.0)return Task.Delay(millisecondsDelay).ContinueWith( _ => action() );
-
Tola Odejayi over 9 yearsWhy would Task.Factory.StartNew not have worked here?
-
JwJosefy over 9 years@sgnsajgon that would be certainly easier to read and to maintain
-
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 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 over 9 yearsThanks, @Frans-Bouma. So TaskCompletionSource is a handy way of converting code that uses the Begin... End... statements into a task?
-
rfornal about 9 yearsPlease place relevant code or documentation here as links can change over time and make this answer irrelevant.
-
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 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 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 about 6 yearsThe problem with this implementation is that this generates a memory leak as the event is never released from obj.Done
-
MicBig over 5 yearsLook at the TaskFactory<TResult>.FromAsync to wrap
Begin.. End...
statements. -
Mandeep Janjua about 5 yearsNo need to use 'async' with 'TaskCompletionSource' as it has already created a task
-
naasking about 4 yearsYou still need TaskCompletionSource when you're managing your own wait handles, and so there is no IAsyncResult with which you can call FromAsync.
-
monomo about 2 yearsNot that this code uses a hidden
async void
- ifawait onCancel()
throws an Exception it will be swallowed up by yourasync void
anddoneReceiving.SetResult(true)
will no execute, thus causing your application to stuck forever on yourawait
in the last line -
Adam B about 2 yearsThis 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 almost 2 yearsPlease 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.