他の非同期パターンと型との相互運用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 から TAP へFrom 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 では、.NET Framework 4 以降で、この変換を行うために 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 クラスと、そのクラスの BeginRead および EndRead メソッドについて考えてみましょう。これらのメソッドは、同期 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 から APM へFrom TAP to APM

既存のインフラストラクチャで APM パターンを使用することを想定している場合は、TAP の実装を用意し、APM の実装を想定している場所でその TAP の実装を使用します。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を実装するため、これは単純なヘルパー関数を使用して実現することができます。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 パターンをラップするより複雑です。EAP パターンのほうが APM パターンよりもバリエーションが多く、構造化の程度が低いためです。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

待機ハンドルから TAP へFrom 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. これを行うには、 T:System.Threading.Tasks.Task の上位にデータ構造をビルドするという「 Taskが追加されました。To 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