When Should Taskcompletionsource<T> Be Used

When should TaskCompletionSource T be used?

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.

TaskCompletionSource - Trying to understand threadless async work

TaskCompletionSource is used to create Task objects that don't execute code.

They're used quite a bit by Microsoft's new async APIs - any time there's I/O-based asynchronous operations (or other non-CPU-based asynchronous operations, like a timeout). Also, any async Task method you write will use TCS to complete its returned Task.

I have a blog post Creating Tasks that discusses different ways to create Task instances. It's written from an async/await perspective (not a TPL perspective), but it still applies here.

Also see Stephen Toub's excellent posts:

  • The Nature of TaskCompletionSource
  • Mechanisms for Creating Tasks
  • await anything; (using TaskCompletionSource to await anything).
  • Using Tasks to implement the APM Pattern (creating Begin/End using TaskCompletionSource).

Should I always complete a TaskCompletionSource?

Do I need to do any sort of cleanup (e.g. make sure tcs.SetCancelled() is called)?

Using a TaskCompletionSource<T> doesn't require any cleanup. In fact, it doesn't even admit any cleanup. So the answer to your question is "no."

The TaskCompletionSource<T> is just a conceptually simple data structure that allows you to push in at most a single thing (a result of type T, an exception, or a cancellation). Its Task property exposes a Task<T> that is just a wrapper around this promised single thing that will be pushed into the TaskCompletionSource<T> at some time in the future. It does not use the task pool whatsoever.

To never push anything into a TaskCompletionSource<T> is perfectly valid. This just corresponds to a Task<T> that will "run" forever and never complete.

TaskCompletionSource usage

What would I receive in result variable if completion source was cancelled?

You code will throw an OperationCancelledException when awaiting a cancelled task. So the result variable will never be set.

You can handle the exception with a try/catch block:

async Task SomeMethod()
{
try
{
.....
Run();
var result = await GetResult();
}
catch(OperationCancelledException)
{
// handle cancelled operation
}
}

Also, SomeMethod should return a Task as void returning async methods are usually only appropriate for event handlers as they must return void. I blog about it briefly here.

In general, if you want an operation to be cancelable you pass in a CancellationToken which the operation must check and pass on to other operations it kicks off. So you pass it all the way down the chain and into your callback.

You can also register a callback with the CancellationToken that cancels the TaskCompletionSource when the token is cancelled so you don't need to do it in your method.

void Run()
{
var cts = new CancellationTokenSource();
var myCompletionSource= new TaskCompletionSource();
cts.Token.Register(() => myCompletionSource.SetCancelled());

TriggerSomeLongLastingLogicWhichWillCallCallBackBelow(cts.Token);
}

void SomeCallback(CancellationToken token)
{
// do some work
....

token.ThrowIfCancellationRequested();

if (someCondition)
{
myCompletionSource.SetResult(<someResult>);
}
else
{
myCompletionSource.SetException(new Exception("error occcured"));
}
}

TaskCompletionSource : When to use SetResult() versus TrySetResult(), etc

I suspect the point is that if there's only one thing which will be setting the result, just call SetResult etc. If you end up calling SetResult twice, that indicates a bug. (Likewise if the TaskCompletionSource has been disposed.)

If you've got several threads which could all be trying to set the result at the same time (e.g. it's there to indicate the first result out of several parallel web service calls) then use TrySetResult, as it's entirely reasonable for multiple threads to "try" to set the result, unaware of whether another thread has already set it.

I've not seen any official guidance on it, but that would make sense.

Should I return ValueTask if I still use TaskCompletionSource to implement asynchrony?

There are pros and cons of each. In the "pro" column:

  1. When returning a result synchronously (i.e. Task<T>), using ValueTask<T> avoids an allocation of the task - however, this doesn't apply for "void" (i.e. non-generic Task), since you can just return Task.CompletedTask
  2. When you are issuing multiple sequential awaitables, you can use a single "value task source" with sequential tokens to reduce allocations

(a special-case of "2" might be amortization via tools like PooledAwait)

It doesn't feel like either of those apply here, but: if in doubt, remember that it is allocation-free to return a Task as a ValueTask (return new ValueTask(task);), which would allow you to consider changing the implementation to an allocation-free one later without breaking the signature. You still pay for the original Task etc of course - but exposing them as ValueTask doesn't add any extra.

In all honesty, I'm not sure I'd worry too much about this in this case, since a delay is ways going to be allocatey.

TaskCompletionSource usage in IO Async methods

Note that for a definitive answer, you would have to ask the author of the code. Barring that, we can only speculate. However, I think it's reasonable to make some inferences with reasonable accuracy…

What is the point of using TaskCompletionSource here and why not to return the task created by Task.Factory.FromAsync() directly?

In this case, it appears to me that the main reason is to allow the implementation to deregister the registered callback CancelIgnoreFailure() before the task is actually completed. This ensures that by the time the client code receives completion notification, the API itself has completely cleaned up from the operation.

A secondary reason might be simply to provide a complete abstraction. I.e. to not allow any of the underlying implementation to "leak" from the method, in the form of a Task object that a caller might inspect or (worse) manipulate in a way that interferes with the correct and reliable operation of the task.

Why is it that the compute-bound methods can be implemented using Task.Run(), but not the I/O bound methods?

You can implement I/O bound operations using Task.Run(), but why would you? Doing so commits a thread to the operation which, for an operation that would not otherwise require a thread, is wasteful.

I/O bound operations generally have support from an I/O completion port and the IOCP thread pool (the threads of which handle completions of an arbitrarily large number of IOCPs) and so it is more efficient to simply use the existing asynchronous I/O API, rather than to use Task.Run() to call a synchronous I/O method.

Should I await a synchronous Task T that uses TaskCompletionSource?

Since you don't have a naturally-asynchronous API, I do not recommend using TaskCompletionSource<T>. You can use the full CancellationToken support with synchronous APIs, as such:

public Stuff GetStuff(CancellationToken token) 
{
var stuff = new Stuff();
using (var stuffGetter = new StuffGetter())
{
stuffGetter.CancelCallback = () => token.IsCancellationRequested;

for (var x = 0; x < 10; x++)
{
token.ThrowIfCancellationRequested();
var thing = stuffGetter.GetSomething();
stuff.AddThing(thing);
}
return stuff;
}
}

When you write task-returning methods, it's important to follow the TAP guidelines. In this case, the naming convention means your AnalyzeStuff should be called AnalyzeStuffAsync.

Can you tell whether I need ConfigureAwait(false) somewhere? If so, how can you tell?

You should use ConfigureAwait(false) unless you need the context in your method (the "context" is usually the UI context for client apps, or the request context for ASP.NET apps). You can find more information in my MSDN article on async best practices.

So, assuming that StuffAnalyzer.Result doesn't have any kind of UI thread dependency or anything like that, I'd write AnalyzeStuffAsync as such:

public async Task<StuffAnalysis> AnalyzeStuffAsync(Stuff stuff, CancellationToken token) 
{
var allTasks = new List<Task>();

using (var stuffAnalyzer = new StuffAnalyzer())
{
foreach (var thing in stuff)
{
allTasks.Add(stuffAnalyzer.AnalyzeThingAsync(thing));
}

await Task.WhenAll(allTasks).ConfigureAwait(false);
}

return stuffAnalyzer.Result;
}

Your GetAndAnalyzeStuffAsync is a more complex situation, where you have both blocking and asynchronous code in the method. In this case, the best approach is to expose it as an asynchronous API but with a clear comment noting that it is blocking.

// <summary>Note: this method is not fully synchronous; it will block the calling thread.</summary>
public async Task<StuffAnalysis> GetAndAnalyzeStuffAsync(CancellationToken token)
{
var stuff = GetStuff(token);
var analysis = await AnalyzeStuff(stuff, token).ConfigureAwait(false);
return analysis;
}

which can simplify to:

// <summary>Note: this method is not fully synchronous; it will block the calling thread.</summary>
public Task<StuffAnalysis> GetAndAnalyzeStuffAsync(CancellationToken token)
{
var stuff = GetStuff(token);
return AnalyzeStuff(stuff, token);
}

How do I deal with the Task.Run() call in the calling code (Main() below)?

You are using it correctly. I describe this situation on my blog. In a Console application it's not common to use Task.Run like this, but there's nothing wrong with doing it this way. Task.Run is usually used to free up the UI thread in a UI application.

I assume Task.Run is more appropriate than Task.Factory.StartNew b/c I'm doing async stuff.

Yes, it is.



Related Topics



Leave a reply



Submit