与其他异步模式和类型互操作Interop with Other Asynchronous Patterns and Types

.NET Framework 1.0 引进了 IAsyncResult 模式,也称为 Asynchronous Programming Model (APM)Begin/End 模式。The .NET Framework 1.0 introduced the IAsyncResult pattern, otherwise known as the Asynchronous Programming Model (APM), or the Begin/End pattern. .NET Framework 2.0 增加了 Event-based Asynchronous Pattern (EAP)The .NET Framework 2.0 added the Event-based Asynchronous Pattern (EAP). 从.NET Framework 4 开始, Task-based Asynchronous Pattern (TAP) 取代了 APM 和 EAP,但能够轻松构建从早期模式中迁移的例程。Starting with the .NET Framework 4, the Task-based Asynchronous Pattern (TAP) supersedes both APM and EAP, but provides the ability to easily build migration routines from the earlier patterns.

本主题内容:In this topic:

任务和异步编程模型 (APM)Tasks and the Asynchronous Programming Model (APM)

从 APM 到 TAPFrom APM to TAP

因为 Asynchronous Programming Model (APM) 模式的结构非常合理,而且能够轻松生成包装,将 APM 实现公开为 TAP 实现。Because the Asynchronous Programming Model (APM) pattern is very structured, it is quite easy to build a wrapper to expose an APM implementation as a TAP implementation. 实际上,自 .NET Framework 4 之后的 .NET Framework 就包含采用 FromAsync 方法重载形式的帮助器例程来实现这种转换。In fact, the .NET Framework, starting with .NET Framework 4, includes helper routines in the form of FromAsync method overloads to provide this translation.

请考虑 Stream 类及其 BeginReadEndRead 方法,它们代表与同步 Read 方法对应的 APM:Consider the Stream class and its BeginRead and EndRead methods, which represent the APM counterpart to the synchronous Read method:

public int Read(byte[] buffer, int offset, int count)
Public Function Read(buffer As Byte(), offset As Integer, 
                     count As Integer) As Integer
public IAsyncResult BeginRead(byte[] buffer, int offset, 
                              int count, AsyncCallback callback, 
                              object state)
Public Function BeginRead(buffer As Byte, offset As Integer, 
                          count As Integer, callback As AsyncCallback, 
                          state As Object) As IAsyncResult
public int EndRead(IAsyncResult asyncResult)
Public Function EndRead(asyncResult As IAsyncResult) As Integer

可以使用 TaskFactory<TResult>.FromAsync 方法来实现此操作的 TAP 包装,如下所示:You can use the TaskFactory<TResult>.FromAsync method to implement a TAP wrapper for this operation as follows:

public static Task<int> ReadAsync(this Stream stream, 
                                  byte[] buffer, int offset, 
                                  int count)
{
    if (stream == null) 
       throw new ArgumentNullException("stream");
    
    return Task<int>.Factory.FromAsync(stream.BeginRead, 
                                       stream.EndRead, buffer, 
                                       offset, count, null);
}
<Extension()>
Public Function ReadAsync(strm As Stream, 
                          buffer As Byte(), offset As Integer, 
                          count As Integer) As Task(Of Integer)
    If strm Is Nothing Then 
       Throw New ArgumentNullException("stream")
    End If   
    
    Return Task(Of Integer).Factory.FromAsync(AddressOf strm.BeginRead, 
                                              AddressOf strm.EndRead, buffer, 
                                              offset, count, Nothing)
End Function

此实现类似于以下内容:This implementation is similar to the following:

 public static Task<int> ReadAsync(this Stream stream, 
                                   byte [] buffer, int offset, 
                                   int count)
 {
    if (stream == null) 
        throw new ArgumentNullException("stream");

    var tcs = new TaskCompletionSource<int>();
    stream.BeginRead(buffer, offset, count, iar =>
                     {
                        try { 
                           tcs.TrySetResult(stream.EndRead(iar)); 
                        }
                        catch(OperationCanceledException) { 
                           tcs.TrySetCanceled(); 
                        }
                        catch(Exception exc) { 
                           tcs.TrySetException(exc); 
                        }
                     }, null);
    return tcs.Task;
}

 <Extension()>
 Public Function ReadAsync(stream As Stream, buffer As Byte(), _
                           offset As Integer, count As Integer) _
                           As Task(Of Integer)
    If stream Is Nothing Then 
        Throw New ArgumentNullException("stream")
    End If    

    Dim tcs As New TaskCompletionSource(Of Integer)()
    stream.BeginRead(buffer, offset, count, 
                     Sub(iar)
                        Try  
                           tcs.TrySetResult(stream.EndRead(iar)) 
                        Catch e As OperationCanceledException  
                           tcs.TrySetCanceled() 
                        Catch e As Exception 
                           tcs.TrySetException(e) 
                        End Try
                     End Sub, Nothing)
    Return tcs.Task
End Function

从 TAP 到 APMFrom TAP to APM

如果现有的基础结构需要 APM 模式,则还需要采用 TAP 实现并在需要 APM 实现的地方使用它。If your existing infrastructure expects the APM pattern, you'll also want to take a TAP implementation and use it where an APM implementation is expected. 由于任务可以组合,并且 Task 类实现 IAsyncResult,您可以使用一个简单的 helper 函数执行此操作。Because tasks can be composed and the Task class implements IAsyncResult, you can use a straightforward helper function to do this. 以下代码使用 Task<TResult> 类的扩展,但可以对非泛型任务使用几乎相同的函数。The following code uses an extension of the Task<TResult> class, but you can use an almost identical function for non-generic tasks.

public static IAsyncResult AsApm<T>(this Task<T> task, 
                                    AsyncCallback callback, 
                                    object state)
{
    if (task == null) 
        throw new ArgumentNullException("task");
    
    var tcs = new TaskCompletionSource<T>(state);
    task.ContinueWith(t => 
                      {
                         if (t.IsFaulted) 
                            tcs.TrySetException(t.Exception.InnerExceptions);
                         else if (t.IsCanceled)    
                            tcs.TrySetCanceled();
                         else 
                            tcs.TrySetResult(t.Result);

                         if (callback != null) 
                            callback(tcs.Task);
                      }, TaskScheduler.Default);
    return tcs.Task;
}
<Extension()>
Public Function AsApm(Of T)(task As Task(Of T), 
                            callback As AsyncCallback, 
                            state As Object) As IAsyncResult
    If task Is Nothing Then 
        Throw New ArgumentNullException("task")
    End If
    
    Dim tcs As New TaskCompletionSource(Of T)(state)
    task.ContinueWith(Sub(antecedent) 
                         If antecedent.IsFaulted Then 
                            tcs.TrySetException(antecedent.Exception.InnerExceptions)
                         ElseIf antecedent.IsCanceled Then    
                            tcs.TrySetCanceled()
                         Else 
                            tcs.TrySetResult(antecedent.Result)
                         End If
                         
                         If callback IsNot Nothing Then 
                            callback(tcs.Task)
                         End If   
                      End Sub, TaskScheduler.Default)
    Return tcs.Task
End Function

现在,请考虑具有以下 TAP 实现的用例:Now, consider a case where you have the following TAP implementation:

public static Task<String> DownloadStringAsync(Uri url)
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)

并且想要提供此 APM 实现:and you want to provide this APM implementation:

public IAsyncResult BeginDownloadString(Uri url, 
                                        AsyncCallback callback, 
                                        object state)
Public Function BeginDownloadString(url As Uri, 
                                    callback As AsyncCallback, 
                                    state As Object) As IAsyncResult
public string EndDownloadString(IAsyncResult asyncResult)
Public Function EndDownloadString(asyncResult As IAsyncResult) As String

以下示例演示了一种向 APM 迁移的方法:The following example demonstrates one migration to APM:

public IAsyncResult BeginDownloadString(Uri url, 
                                        AsyncCallback callback, 
                                        object state)
{
   return DownloadStringAsync(url).AsApm(callback, state);
}

public string EndDownloadString(IAsyncResult asyncResult)
{
   return ((Task<string>)asyncResult).Result;
}
Public Function BeginDownloadString(url As Uri, 
                                    callback As AsyncCallback, 
                                    state As Object) As IAsyncResult
   Return DownloadStringAsync(url).AsApm(callback, state)
End Function

Public Function EndDownloadString(asyncResult As IAsyncResult) As String
   Return CType(asyncResult, Task(Of String)).Result
End Function

任务和基于事件的异步模式 (EAP)Tasks and the Event-based Asynchronous Pattern (EAP)

包装 Event-based Asynchronous Pattern (EAP) 实现比包装 APM 模式更为复杂,因为与 APM 模式相比,EAP 模式的变体更多,结构更少。Wrapping an Event-based Asynchronous Pattern (EAP) implementation is more involved than wrapping an APM pattern, because the EAP pattern has more variation and less structure than the APM pattern. 为了演示,以下代码包装了 DownloadStringAsync 方法。To demonstrate, the following code wraps the DownloadStringAsync method. DownloadStringAsync 接受 URI,在下载时引发 DownloadProgressChanged 事件,以报告进度的多个统计信息,并在完成时引发 DownloadStringCompleted 事件。DownloadStringAsync accepts a URI, raises the DownloadProgressChanged event while downloading in order to report multiple statistics on progress, and raises the DownloadStringCompleted event when it's done. 最终在指定 URI 中返回一个字符串,其中包含页面内容。The final result is a string that contains the contents of the page at the specified URI.

 public static Task<string> DownloadStringAsync(Uri url)
 {
     var tcs = new TaskCompletionSource<string>();
     var wc = new WebClient();
     wc.DownloadStringCompleted += (s,e) =>
         {
             if (e.Error != null) 
                tcs.TrySetException(e.Error);
             else if (e.Cancelled) 
                tcs.TrySetCanceled();
             else 
                tcs.TrySetResult(e.Result);
         };
     wc.DownloadStringAsync(url);
     return tcs.Task;
}
 Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)
     Dim tcs As New TaskCompletionSource(Of String)()
     Dim wc As New WebClient()
     AddHandler wc.DownloadStringCompleted, Sub(s,e) 
             If e.Error IsNot Nothing Then 
                tcs.TrySetException(e.Error)
             ElseIf e.Cancelled Then 
                tcs.TrySetCanceled()
             Else 
                tcs.TrySetResult(e.Result)
             End If   
         End Sub
     wc.DownloadStringAsync(url)
     Return tcs.Task
End Function

任务和等待句柄Tasks and Wait Handles

从等待句柄到 TAPFrom Wait Handles to TAP

虽然等待句柄不能实现异步模式,但高级开发人员可以在设置等待句柄时使用 WaitHandle 类和 ThreadPool.RegisterWaitForSingleObject 方法实现异步通知。Although wait handles don't implement an asynchronous pattern, advanced developers may use the WaitHandle class and the ThreadPool.RegisterWaitForSingleObject method for asynchronous notifications when a wait handle is set. 可以包装 RegisterWaitForSingleObject 方法以在等待句柄中启用针对任何同步等待的基于任务的替代方法:You can wrap the RegisterWaitForSingleObject method to enable a task-based alternative to any synchronous wait on a wait handle:

public static Task WaitOneAsync(this WaitHandle waitHandle)
{
    if (waitHandle == null) 
        throw new ArgumentNullException("waitHandle");

    var tcs = new TaskCompletionSource<bool>();
    var rwh = ThreadPool.RegisterWaitForSingleObject(waitHandle, 
        delegate { tcs.TrySetResult(true); }, null, -1, true);
    var t = tcs.Task;
    t.ContinueWith( (antecedent) => rwh.Unregister(null));
    return t;
}
<Extension()>
Public Function WaitOneAsync(waitHandle As WaitHandle) As Task
    If waitHandle Is Nothing Then 
        Throw New ArgumentNullException("waitHandle")
    End If
    
    Dim tcs As New TaskCompletionSource(Of Boolean)()
    Dim rwh As RegisteredWaitHandle = ThreadPool.RegisterWaitForSingleObject(waitHandle, 
        Sub(state, timedOut) 
           tcs.TrySetResult(True)
        End Sub, Nothing, -1, True)
    Dim t = tcs.Task
    t.ContinueWith( Sub(antecedent) 
                       rwh.Unregister(Nothing)
                    End Sub)
    Return t
End Function

使用此方法,可以在异步方法中使用现有 WaitHandle 实现。With this method, you can use existing WaitHandle implementations in asynchronous methods. 例如,如果想要限制在任何特定时间执行的异步操作的数目,则可以利用一个信号量( System.Threading.SemaphoreSlim 对象)。For example, if you want to throttle the number of asynchronous operations that are executing at any particular time, you can utilize a semaphore (a System.Threading.SemaphoreSlim object). 可以将并发运行的操作数目限制到 N ,方法为:初始化到 N的信号量的数目、在想要执行操作时等待信号量,并在完成操作时释放信号量:You can throttle to N the number of operations that run concurrently by initializing the semaphore’s count to N, waiting on the semaphore any time you want to perform an operation, and releasing the semaphore when you’re done with an operation:

static int N = 3;

static SemaphoreSlim m_throttle = new SemaphoreSlim(N, N);

static async Task DoOperation()
{
    await m_throttle.WaitAsync();
    // do work
    m_throttle.Release();
}
Shared N As Integer = 3

Shared m_throttle As New SemaphoreSlim(N, N)

Shared Async Function DoOperation() As Task
    Await m_throttle.WaitAsync()
    ' Do work.
    m_throttle.Release()
End Function

还可以构建不依赖等待句柄就完全可以处理任务的异步信号量。You can also build an asynchronous semaphore that does not rely on wait handles and instead works completely with tasks. 若要执行此操作,可以使用 使用基于任务的异步模式 中所述的用于在 TaskTo do this, you can use techniques such as those discussed in Consuming the Task-based Asynchronous Pattern for building data structures on top of Task.

从 TAP 到等待句柄From TAP to Wait Handles

正如前面所述, Task 类实现 IAsyncResult,且该实现公开 IAsyncResult.AsyncWaitHandle 属性,该属性会返回在 Task 完成时设置的等待句柄。As previously mentioned, the Task class implements IAsyncResult, and that implementation exposes an IAsyncResult.AsyncWaitHandle property that returns a wait handle that will be set when the Task completes. 可以获得 WaitHandleTask ,如下所示:You can get a WaitHandle for a Task as follows:

WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle

请参阅See also