Udostępnij za pośrednictwem


Współdziałanie z innymi wzorcami asynchronicznymi i typami

Krótka historia asynchronicznych wzorców na platformie .NET:

Zadania i asynchroniczny model programowania (APM)

Od APM do TAP

Ponieważ wzorzec modelu programowania asynchronicznego (APM) jest ustrukturyzowany, dość łatwo jest utworzyć otokę, aby uwidocznić implementację APM jako implementację tap. Program .NET Framework 4 i nowsze wersje zawierają procedury pomocnicze w postaci FromAsync przeciążeń metod w celu zapewnienia tego tłumaczenia.

Rozważ klasę Stream i jej BeginReadEndRead metody, które reprezentują odpowiednik APM z metodą synchroniczną Read :

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

Za pomocą TaskFactory<TResult>.FromAsync metody można zaimplementować otokę TAP dla tej operacji w następujący sposób:

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

Ta implementacja jest podobna do następującej:

 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

Od naciśnięcia do APM

Jeśli istniejąca infrastruktura oczekuje wzorca APM, należy również wykonać implementację tap i użyć jej tam, gdzie oczekiwana jest implementacja APM. Ponieważ zadania mogą być komponowane, a Task klasa implementuje IAsyncResult, możesz użyć prostej funkcji pomocniczej, aby to zrobić. Poniższy kod używa rozszerzenia Task<TResult> klasy, ale można użyć niemal identycznej funkcji dla zadań niegenerycznych.

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

Teraz rozważmy przypadek, w którym masz następującą implementację interfejsu TAP:

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

i chcesz udostępnić tę implementację APM:

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

W poniższym przykładzie pokazano jedną migrację do usługi 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

Zadania i wzorzec asynchroniczny oparty na zdarzeniach (EAP)

Zawijanie implementacji asynchronicznego wzorca opartego na zdarzeniach (EAP) jest bardziej zaangażowane niż zawijanie wzorca APM, ponieważ wzorzec protokołu EAP ma większą zmienność i mniejszą strukturę niż wzorzec APM. Aby to zademonstrować, poniższy kod opakowuje metodę DownloadStringAsync . DownloadStringAsync Akceptuje identyfikator URI, zgłasza DownloadProgressChanged zdarzenie podczas pobierania w celu raportowania wielu statystyk postępu i zgłasza zdarzenie po zakończeniu DownloadStringCompleted . Końcowy wynik jest ciągiem zawierającym zawartość strony w określonym identyfikatorze 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

Zadania i uchwyty oczekiwania

Z dojścia oczekiwania do naciśnięcia

Mimo że dojścia oczekiwania nie implementują wzorca asynchronicznego, zaawansowani deweloperzy mogą używać WaitHandle klasy i ThreadPool.RegisterWaitForSingleObject metody powiadomień asynchronicznych po ustawieniu uchwytu oczekiwania. Możesz opakowować metodę RegisterWaitForSingleObject , aby włączyć alternatywę opartą na zadaniach dowolnego synchronicznego oczekiwania na dojściu oczekiwania:

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

Za pomocą tej metody można użyć istniejących WaitHandle implementacji w metodach asynchronicznych. Jeśli na przykład chcesz ograniczyć liczbę operacji asynchronicznych wykonywanych w dowolnym momencie, możesz użyć semafora ( System.Threading.SemaphoreSlim obiektu). Możesz ograniczyć do N liczbę operacji uruchamianych współbieżnie, inicjując liczbę semaforów do N, czekając na semafor w dowolnym momencie, gdy chcesz wykonać operację, i zwalniając semafor po zakończeniu operacji:

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

Można również utworzyć asynchroniczny semafor, który nie polega na uchwytach oczekiwania i zamiast tego działa całkowicie z zadaniami. W tym celu można użyć technik, takich jak te omówione w temacie Korzystanie ze wzorca asynchronicznego opartego na zadaniach na potrzeby tworzenia struktur danych na podstawie Taskelementu .

Z interfejsu TAP do uchwytów oczekiwania

Jak wspomniano wcześniej, klasa implementuje IAsyncResultelement , a implementacja uwidacznia właściwość zwracającą IAsyncResult.AsyncWaitHandle uchwyt oczekiwania, który zostanie ustawiony po zakończeniuTask.Task Element dla elementu Task można uzyskać WaitHandle w następujący sposób:

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

Zobacz też