Microsoft will give us lots of awaitables in .NET 4.5, but there are some situations where we want to create our own awaitable. Task<T> and Task are the easiest awaitable types to work with in .NET, so today we’ll look at different ways awaitable Task objects can be constructed.
All Task objects fall into one of two types: “code” and “event”. Code-based tasks have a delegate that they are trying to run. Event-based tasks have no code; they’re just waiting for some event to take place.
Tasks as Events
Tasks without code can represent any kind of event. The most common examples are I/O completion events, but event-based tasks can actually wrap any kind of event.
To create an event-based task, use the TaskCompletionSource<TResult> class:
Remember that Task<T> and Task are awaitable, so you can await the result of MyIntegerEventAsync even though it’s not an async method.
You probably won’t have to use TaskCompletionSource<TResult> directly; you can use TaskFactory.FromAsync to wrap any operation that uses IAsyncResult (and most existing asynchronous I/O methods do use IAsyncResult). Internally, FromAsync uses a technique just like TaskCompletionSource<TResult>.
Nito.AsyncEx includes an AsyncFactory type which works like TaskFactory.FromAsync but is slightly easier to use and supports more arguments. It also includes a (non-generic) TaskCompletionSource, which is easier to use when creating (non-generic) Tasks.
Update (2014-12-01): For more details, see Recipes 7.1, 7.2, and 7.3 in my Concurrency Cookbook.
Tasks as Code
Remember (from our async intro post) that the async keyword does not run the method on a background thread. However, you can use Task.Run to run code on a background thread:
Task.Run will take a delegate and run it on the thread pool for you. It wraps the delegate into a Task<T> or Task, and the Task wrapper takes care of all the error handling and other stuff correctly.
What about other threads? What if you don’t want to run your code on the thread pool?
If you have a particular context in which you want to run your code, you can use a TaskFactory initialized with a TaskScheduler that targets your context. You can then call TaskFactory.StartNew to run your code in that context. In fact, Task.Run is just a shorthand for Task.Factory.StartNew.
It’s possible to capture the current synchronization context into a TaskScheduler by calling TaskScheduler.FromCurrentSynchronizationContext, and then use it later (e.g., from a background thread) to run code within that context. Normally there are better ways to do this, but it is an option.
Before async/await, this was a good way to send progress reports from a background task to the UI. With async/await, there is now a better way.
Writing your own TaskScheduler is possible, but frustrating due to the lack of MSDN documentation. Fortunately, it’s almost never necessary.
There are some pretty cool tricks we can pull off using the built-in TaskScheduler/TaskFactory types. We’ll look at more advanced TaskScheduler situations in later posts.
Update (2014-12-01): For more details, see Recipes 7.4 and 3.4 in my Concurrency Cookbook.
Tasks as Async Methods
This is a special case of Task creation - and it’s easy to overlook!
The flat-out easiest way to create Task objects is to write an asynchronous method:
We do not create a Task<int> in our code, but the compiler rewrites our code so that a Task<int> is created and returned. When the method completes, the task completes. This type of task is actually an event-based task, since an event (the method returning) causes the task to complete.
That’s the easiest way to create Task objects! However, it only works if you’re building on existing awaitables; if you aren’t in this situation, then you should use Task.Run or TaskCompletionSource<TResult>.
Update (2014-12-01): For more details, see Section 1.2 and Chapter 2 in my Concurrency Cookbook.