The Task Parallel Library (TPL) provides a set of “FromAsync” helper methods that create a Task or a Task<TResult> to represent an invocation of an APM method pair, i.e. BeginXx / EndXx. There are, however, two different flavors among these overloads: ones that accept an IAsyncResult “asyncResult” as the first parameter, and ones that accept a Func<…,AsyncCallback,object,IAsyncResult> “beginMethod” as the first parameter. What’s the difference, and why would you choose one over the other?
The fundamental difference between the two is in how the endMethod delegate is invoked. When FromAsync is given a beginMethod delegate, it passes to that beginMethod an AsyncCallback that will invoke the endMethod. As with any APM implemention, it’s up to the BeginXx’s implementation to eventually invoke this AsyncCallback when the async operation has completed. In contrast, the IAsyncResult interface doesn’t provide any mechanism via which to provide a callback that will be invoked when the operation represented by the IAsyncResult is completed, so when FromAsync is handed an IAsyncResult, it needs to monitor the IAsyncResult’s WaitHandle.
Monitoring the IAsyncResult’s WaitHandle (as returned from its AsyncWaitHandle property) has a few implications. First, since the APM pattern is all about asynchrony, most IAsyncResult implementations strive to avoid allocating the WaitHandle, only doing so lazily if it’s actually requested. Thus, by accessing the IAsyncResult’s AsyncWaitHandle property, FromAsync is likely forcing allocation of a WaitHandle (and the associated operating system primitives) that otherwise wouldn’t need to be allocated. Second, the only way to “monitor” a WaitHandle to know when it’s been set is to wait on it, which is a potentially blocking operation. Obviously, FromAsync isn’t going to block the current thread by waiting on this handle (that wouldn’t be very async of it), so instead it relies on the ThreadPool. It could queue a work item to block on the handle, but that would end up burning a ThreadPool thread for each such call. Instead, FromAsync relies on ThreadPool.RegisterWaitForSingleObject, a function that is specifically geared towards using the ThreadPool to more efficiently wait for handles. According to the MSDN documentation, it does so by waiting on multiple handles from a single thread via WaitForMultipleObjects; however, there are a limit to how many handles it can wait on from each thread, so while this will end up burning fewer threads than if we blocked one thread per handle, it’s still far from ideal.
As such, if you have a choice between using FromAsync(asyncResult, …) and using FromAsync(beginMethod, …), pick the latter that uses a delegate instead of an IAsyncResult. When might you choose to use the former? The IAsyncResult overloads can still be useful in a few cases. For example, there are some BeginXx methods with signatures that don’t exactly conform to the beginMethod functions accepted by the built-in FromAsync overloads, e.g. they have more parameters than the built-in versions can handle. In such a case, you could choose to either write your own FromAsync method to accommodate the custom signature, or you could use an IAsyncResult overload, since it doesn’t care about the shape of the BeginXx method, only about its result.