There are several nice API-level enhancements to the Task Parallel Library in .NET 4.6, which you can grab a preview of as part of the Visual Studio 2015 CTP.
Task.From*
.NET 4.5 had a Task.FromResult method. This method makes it quick and easy to manufacture a new Task for a known result value, and is particularly useful when implementing a Task-returning method to complete synchronously. However, Task didn't expose corresponding methods for creating canceled or faulted tasks; instead, developers needed to manually create such tasks, such as by using a TaskCompletionSource, e.g.
public static Task
{
var tcs = new TaskCompletionSource
tcs.SetException(exc);
return tcs.Task;
}
Now in .NET 4.6, developers no longer need to write this boilerplate, as Task exposes several new static helpers, Task.FromCanceled and Task.FromException. Imagine you were implementing a Stream-derived type whose Read method simply read and returned some data already in memory; it would likely be more efficient for ReadAsync to simply do the work synchronously rather than scheduling the memory reads to be done asynchronously. Such functionality can now be done with fairly minimal boilerplate using these new APIs, e.g.
public class MyMemoryStream : Stream
{
...
public override Task
{
if (cancellationToken.IsCancellationRequested)
return Task.FromCanceled
try
{
return Task.FromResult(Read(buffer, offset, count));
}
catch (Exception exc)
{
return Task.FromException(exc);
}
}
...
}
Task{Creation/Continuation}Options.RunContinuationsAsynchronously
TaskCompletionSource
"I talked about a ramification of calling {Try}Set* methods on TaskCompletionSource
This is a not-uncommon issue that folks using TaskCompletionSource to build such types face and need to work around. In this particular case, I avoided the cited problem by storing the TaskCompletionSource
You can see another example of this in SemaphoreSlim.Release. SemaphoreSlim.WaitAsync/Release are often used in async methods to provide mutual exclusion without blocking, e.g.
private SemaphoreSlim _gate = new SemaphoreSlim(1, 1);
...
private async Task WorkAsync()
{
await _gate.WaitAsync().ConfigureAwait(false);
try
{
... // work here
}
finally { _gate.Release(); }
}
Now imagine that you have lots of calls to WorkAsync:
await Task.WhenAll(from i in Enumerable.Range(0, 10000) select WorkAsync());
We've just created 10,000 calls to WorkAsync that will be appropriately serialized on the semaphore. One of the tasks will enter the critical region, and the others will queue up on the WaitAsync call, inside SemaphoreSlim effectively enqueueing the task to be completed when someone calls Release. If Release completed that Task synchronously, then when the first task calls Release, it'll synchronously start executing the second task, and when it calls Release, it'll synchronously start executing the third task, and so on. If the "//work here" section of code above didn't include any awaits that yielded, then we're potentially going to stack dive here and eventually potentially blow out the stack. In large part for this reason, SemaphoreSlim.Release doesn't actually complete the task synchronously; rather, Release queues the completion of the task so that it happens asynchronously from the caller.
To make these kinds of issues easier to address, and also to make them a bit more obvious, in .NET 4.6 the TaskCreationOptions and TaskContinuationOptions enums include a new value: RunContinuationsAsynchronously. When a Task created with this option completes, it won't even try to invoke continuations synchronously... it'll simply invoke all of the continuations asynchronously as if none had asked to be executed synchronously if possible. There are already multiple reasons why a continuation that requested synchronous execution may not run synchronously, e.g. if the TaskScheduler denies it, and now the developer creating the task can explicitly deny such requests.
TaskCompletionSource
public Task WaitAsync()
{
lock (m_waits)
{
if (m_signaled)
{
m_signaled = false;
return s_completed;
}
else
{
var tcs = new TaskCompletionSource
m_waits.Enqueue(tcs);
return tcs.Task;
}
}
}
public void Set()
{
lock (m_waits)
{
if (m_waits.Count > 0)
m_waits.Dequeue().SetResult(true);
else if (!m_signaled)
m_signaled = true;
}
}
Enjoy!