Partager via


Interopérabilité avec d’autres modèles et types asynchrones

Un bref historique des modèles asynchrones dans .NET :

Tâches et modèle de programmation asynchrone (APM)

D’APM à TAP

Étant donné que le modèle APM (Asynchrone Programming Model) est structuré, il est très facile de créer un wrapper pour exposer une implémentation APM en tant qu’implémentation TAP. .NET Framework 4 et les versions ultérieures incluent des routines d’assistance sous forme de surcharges de méthodes FromAsync pour fournir cette traduction.

Considérez la classe Stream et ses méthodes BeginRead et EndRead, qui représentent l’équivalent APM de la méthode synchrone 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

Vous pouvez utiliser la TaskFactory<TResult>.FromAsync méthode pour implémenter un wrapper TAP pour cette opération comme suit :

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

Cette implémentation est similaire à ce qui suit :

 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

De TAP à APM

Si votre infrastructure existante attend le modèle APM, vous devez également prendre une implémentation TAP et l’utiliser là où une implémentation APM est attendue. Étant donné que les tâches peuvent être composées et que la classe Task implémente IAsyncResult, vous pouvez utiliser une fonction d’assistance simple pour cela. Le code suivant utilise une extension de la Task<TResult> classe, mais vous pouvez utiliser une fonction presque identique pour les tâches non génériques.

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

À présent, considérez un cas où vous disposez de l’implémentation TAP suivante :

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

et que vous souhaitez fournir cette implémentation 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

L’exemple suivant illustre une migration vers 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

Tâches et modèle asynchrone basé sur les événements (EAP)

L’habillage d’une implémentation de modèle asynchrone basé sur les événements (EAP) est plus impliqué que l’habillage d’un modèle APM, car le modèle EAP a plus de variantes et moins de structure que le modèle APM. Pour illustrer, le code suivant encapsule la DownloadStringAsync méthode. DownloadStringAsync accepte un URI, déclenche l’événement DownloadProgressChanged lors du téléchargement afin de signaler plusieurs statistiques sur la progression et déclenche l’événement DownloadStringCompleted lorsqu’il est terminé. Le résultat final est une chaîne qui contient le contenu de la page à l’URI spécifié.

 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

Tâches et handles d’attente

handles d’attente vers TAP

Bien que les handles d’attente n’implémentent pas de modèle asynchrone, les développeurs avancés peuvent utiliser la WaitHandle classe et la ThreadPool.RegisterWaitForSingleObject méthode pour les notifications asynchrones lorsqu’un handle d’attente est défini. Vous pouvez envelopper la méthode RegisterWaitForSingleObject pour autoriser une alternative basée sur les tâches pour toute attente synchrone sur un handle d’attente :

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

Avec cette méthode, vous pouvez utiliser des implémentations existantes WaitHandle dans des méthodes asynchrones. Par exemple, si vous souhaitez limiter le nombre d’opérations asynchrones qui s’exécutent à un moment donné, vous pouvez utiliser un sémaphore (objet System.Threading.SemaphoreSlim ). Vous pouvez limiter à N le nombre d’opérations qui s’exécutent simultanément en initialisant le nombre de sémaphores sur N, en attendant le sémaphore à tout moment où vous souhaitez effectuer une opération et en libérant le sémaphore lorsque vous avez terminé avec une opération :

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

Vous pouvez également créer un sémaphore asynchrone qui ne s'appuie pas sur des mécanismes d'attente et fonctionne entièrement avec des tâches. Pour ce faire, vous pouvez utiliser des techniques telles que celles décrites dans Consommation du modèle asynchrone basé sur les tâches pour créer des structures de données en plus de Task.

TAP vers handles d’attente

Comme mentionné précédemment, la Task classe implémente IAsyncResult, et cette implémentation expose une propriété qui retourne un handle d’attente IAsyncResult.AsyncWaitHandle qui sera défini une fois l’opération Task terminée. Vous pouvez obtenir un WaitHandle pour un Task comme suit :

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

Voir aussi