Dela via


Interop med andra asynkrona mönster och typer

En kort historik över asynkrona mönster i .NET:

Uppgifter och APM (Asynchronous Programming Model)

Från APM till TAP

Eftersom APM-mönstret (APM) är strukturerat är det ganska enkelt att skapa en wrapper för att exponera en APM-implementering som en TAP-implementering. .NET Framework 4 och senare versioner innehåller hjälprutiner i form av FromAsync metodöverbelastningar för att tillhandahålla den här översättningen.

Stream Tänk på klassen och dess BeginRead metoderEndRead, som representerar APM-motsvarigheten till den synkrona Read metoden:

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

Du kan använda TaskFactory<TResult>.FromAsync metoden för att implementera en TAP-omslutning för den här åtgärden på följande sätt:

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

Den här implementeringen liknar följande:

 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

Från TAP till APM

Om din befintliga infrastruktur förväntar sig APM-mönstret vill du också implementera TAP och använda den där en APM-implementering förväntas. Eftersom aktiviteter kan skapas och Task klassen implementeras IAsyncResultkan du använda en enkel hjälpfunktion för att göra detta. Följande kod använder ett tillägg för Task<TResult> klassen, men du kan använda en nästan identisk funktion för icke-generiska uppgifter.

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

Nu bör du överväga ett fall där du har följande TAP-implementering:

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

och du vill tillhandahålla den här APM-implementeringen:

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

I följande exempel visas en migrering till 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

Uppgifter och det händelsebaserade Asynkrona mönstret (EAP)

Att omsluta en händelsebaserad EAP-implementering (Asynchronous Pattern) är mer involverat än att omsluta ett APM-mönster, eftersom EAP-mönstret har mer variation och mindre struktur än APM-mönstret. För att demonstrera omsluter DownloadStringAsync följande kod metoden. DownloadStringAsync accepterar en URI, genererar DownloadProgressChanged händelsen vid nedladdning för att rapportera flera statistik om förloppet och genererar DownloadStringCompleted händelsen när den är klar. Slutresultatet är en sträng som innehåller innehållet på sidan vid den angivna URI:n.

 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

Uppgifter och väntehandtag

Från Väntehandtag till TAP

Även om väntehandtag inte implementerar ett asynkront mönster kan avancerade utvecklare använda WaitHandle klassen och ThreadPool.RegisterWaitForSingleObject metoden för asynkrona meddelanden när ett väntehandtag har angetts. Du kan omsluta RegisterWaitForSingleObject metoden för att aktivera ett aktivitetsbaserat alternativ till synkron väntan på ett väntehandtag:

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

Med den här metoden kan du använda befintliga WaitHandle implementeringar i asynkrona metoder. Om du till exempel vill begränsa antalet asynkrona åtgärder som körs vid en viss tidpunkt kan du använda en semafor (ett System.Threading.SemaphoreSlim objekt). Du kan begränsa till N antalet åtgärder som körs samtidigt genom att initiera semaforantalet till N, vänta på semaforen när du vill utföra en åtgärd och släppa semaforen när du är klar med en åtgärd:

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

Du kan också skapa en asynkron semafor som inte förlitar sig på väntehandtag och i stället fungerar helt med uppgifter. För att göra detta kan du använda tekniker som de som beskrivs i Använda det aktivitetsbaserade asynkrona mönstret för att skapa datastrukturer ovanpå Task.

Från TAP till Väntehandtag

Som tidigare nämnts Task implementerar IAsyncResultklassen , och den implementeringen exponerar en IAsyncResult.AsyncWaitHandle egenskap som returnerar ett väntehandtag som kommer att anges när den Task är klar. Du kan få en WaitHandle för en Task enligt följande:

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

Se även