This chapter covers
- Creating Task objects that you can control
- TaskCompletionSource and TaskCompletionSource<T>
- Completing a Task successfully and with an error, and canceling a Task
- Adapting old and nonstandard asynchronous APIs to use tasks
- Using TaskCompletionSource to implement asynchronous initialization
- Using TaskCompletionSource to implement asynchronous data structures
Until now, we’ve talked about using async/await to consume asynchronous APIs. In this chapter, we’ll talk about writing your own asynchronous APIs. Common reasons for doing so include adapting a non-Task–based asynchronous API so that it can be used with await, using await to asynchronously wait for events that happen in your application, or creating an async/await-compatible thread-safe data structure, just to give a few examples. (Spoiler: We will write code for those examples later in this chapter.)
Way back in chapter 3, to understand how the async and await keywords work, we took a method that used async/await and transformed it into an equivalent method that produces exactly the same asynchronous operation but doesn’t use async and await. Back then, we didn’t know how to create Task objects, but we did know that await can be implemented by a callback (specifically, Task.ContinueWith). So instead of a Task, we used callbacks to report the operation results. To make this change, we modified the method signature from
Task<int> GetBitmapWidth(string path)
to