Freigeben über


Interoperabilität mit anderen asynchronen Mustern und Typen

Ein kurzer Verlauf asynchroner Muster in .NET:

Aufgaben und das asynchrone Programmiermodell (APM)

Von APM zu TAP

Da das APM-Muster (Asynchronous Programming Model) strukturiert ist, ist es ganz einfach, einen Wrapper zu erstellen, um eine APM-Implementierung als TAP-Implementierung verfügbar zu machen. .NET Framework 4 und höhere Versionen enthalten Hilfsroutinen in Form von FromAsync Methodenüberladungen, um diese Übersetzung bereitzustellen.

Berücksichtigen Sie die Stream Klasse und ihre BeginRead und EndRead Methoden, die das APM-Gegenstück zur synchronen Read Methode darstellen:

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

Sie können die TaskFactory<TResult>.FromAsync Methode verwenden, um einen TAP-Wrapper für diesen Vorgang wie folgt zu implementieren:

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

Diese Implementierung ähnelt folgendem:

 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

Von TAP zu APM

Wenn Ihre vorhandene Infrastruktur das APM-Muster erwartet, möchten Sie auch eine TAP-Implementierung einsetzen und diese dort nutzen, wo eine APM-Implementierung erwartet wird. Da Aufgaben zusammengesetzt werden können und die Task-Klasse IAsyncResult implementiert, können Sie dazu eine einfache Hilfsfunktion verwenden. Der folgende Code verwendet eine Erweiterung der Task<TResult> Klasse, aber Sie können eine nahezu identische Funktion für nicht generische Aufgaben verwenden.

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

Betrachten Sie nun einen Fall, in dem Sie über die folgende TAP-Implementierung verfügen:

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

und Sie möchten diese APM-Implementierung bereitstellen:

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

Im folgenden Beispiel wird eine Migration zu APM veranschaulicht:

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

Aufgaben und das ereignisbasierte asynchrone Muster (EAP)

Das Einbinden einer Implementierung des ereignisbasierten asynchronen Musters (EAP) ist aufwendiger als das Einbinden eines APM-Musters, da das EAP-Muster mehr Variationen und weniger Struktur aufweist als das APM-Muster. Der folgende Code umschließt die DownloadStringAsync Methode, um dies zu veranschaulichen. DownloadStringAsync akzeptiert einen URI, löst das DownloadProgressChanged Ereignis beim Herunterladen aus, um mehrere Statistiken zum Fortschritt zu melden, und löst das DownloadStringCompleted Ereignis aus, wenn es fertig ist. Das endgültige Ergebnis ist eine Zeichenfolge, die den Inhalt der Seite am angegebenen URI enthält.

 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

Aufgaben und Wait-Handles

von den Wait-Handles zu TAP

Obwohl wait handles kein asynchrones Muster implementieren, verwenden erweiterte Entwickler möglicherweise die WaitHandle Klasse und die ThreadPool.RegisterWaitForSingleObject Methode für asynchrone Benachrichtigungen, wenn ein Wartehandle festgelegt ist. Sie können die RegisterWaitForSingleObject -Methode umschließen, um eine aufgabenbasierte Alternative zu jedem synchronen Wartevorgang in einem Wait-Handle zu ermöglichen:

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

Mit dieser Methode können Sie vorhandene WaitHandle Implementierungen in asynchronen Methoden verwenden. Wenn Sie beispielsweise die Anzahl der asynchronen Vorgänge, die zu einem bestimmten Zeitpunkt ausgeführt werden, drosseln möchten, können Sie ein Semaphor (ein System.Threading.SemaphoreSlim Objekt) verwenden. Sie können die Anzahl der Vorgänge, die gleichzeitig ausgeführt werden, drosseln, indem Sie die Anzahl des Semaphors auf N initialisieren, jedes Mal auf das Semaphor warten, wenn Sie einen Vorgang ausführen möchten, und das Semaphor loslassen, wenn Sie mit einem Vorgang fertig sind:

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

Sie können auch ein asynchrones Semaphor erstellen, das nicht auf Wait-Handles beruht und stattdessen vollständig mit Aufgaben funktioniert. Dazu können Sie Techniken verwenden, wie sie in Verwendung des aufgabenbasierten asynchronen Musters besprochen werden, um Datenstrukturen auf Task zu erstellen.

von TAP zu Wait-Handles

Wie bereits erwähnt, implementiert die Klasse Task das IAsyncResult, und diese Implementierung stellt eine IAsyncResult.AsyncWaitHandle-Eigenschaft bereit, die ein Warthandle zurückgibt, das festgelegt wird, nachdem der Task abgeschlossen ist. Sie können ein WaitHandle für Task abrufen, wie folgt:

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

Siehe auch