Share via


Interoperabilidade com outros padrões e tipos assíncronos

Um breve histórico de padrões assíncronos no .NET:

  • O .NET Framework 1.0 introduziu o IAsyncResult padrão, também conhecido como APM (Asynchronous Programming Model) ou o Begin/End padrão.
  • O .NET Framework 2.0 adicionou o padrão assíncrono baseado em evento (EAP).
  • O .NET Framework 4 introduziu o Padrão Assíncrono Baseado em Tarefas (TAP), que substitui o APM e o EAP e fornece a capacidade de criar facilmente rotinas de migração a partir dos padrões anteriores.

Tarefas e o modelo de programação assíncrona (APM)

Da APM à TAP

Como o padrão APM (Asynchronous Programming Model) é estruturado, é muito fácil criar um wrapper para expor uma implementação APM como uma implementação TAP. O .NET Framework 4 e versões posteriores incluem rotinas auxiliares na forma de sobrecargas de FromAsync método para fornecer essa tradução.

Considere a classe e seus StreamBeginRead métodos and EndRead , que representam a contraparte APM para o método síncrono 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

Você pode usar o TaskFactory<TResult>.FromAsync método para implementar um wrapper TAP para esta operação da seguinte maneira:

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

Esta implementação é semelhante à seguinte:

 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

Da TAP à APM

Se a sua infraestrutura existente espera o padrão APM, você também vai querer pegar uma implementação TAP e usá-la onde uma implementação APM é esperada. Como as tarefas podem ser compostas e a classe implementa, TaskIAsyncResultvocê pode usar uma função auxiliar direta para fazer isso. O código a seguir usa uma extensão da Task<TResult> classe, mas você pode usar uma função quase idêntica para tarefas não genéricas.

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

Agora, considere um caso em que você tem a seguinte implementação TAP:

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

e você deseja fornecer esta implementação 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

O exemplo a seguir demonstra uma migração para o 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

Tarefas e o padrão assíncrono baseado em eventos (EAP)

Encapsular uma implementação de padrão assíncrono baseado em evento (EAP) está mais envolvido do que encapsular um padrão APM, porque o padrão EAP tem mais variação e menos estrutura do que o padrão APM. Para demonstrar, o código a seguir encapsula o DownloadStringAsync método. DownloadStringAsync aceita um URI, gera o evento durante o DownloadProgressChanged download para relatar várias estatísticas sobre o progresso e gera o DownloadStringCompleted evento quando ele é concluído. O resultado final é uma cadeia de caracteres que contém o conteúdo da página no URI especificado.

 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

Tarefas e alças de espera

Das alças de espera à TAP

Embora as alças de espera não implementem um padrão assíncrono, os desenvolvedores avançados podem usar a WaitHandle classe e o método para notificações assíncronas ThreadPool.RegisterWaitForSingleObject quando uma alça de espera é definida. Você pode encapsular o método para habilitar uma alternativa baseada em tarefa para qualquer espera síncrona RegisterWaitForSingleObject em uma alça de espera:

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

Com esse método, você pode usar implementações existentes WaitHandle em métodos assíncronos. Por exemplo, se você quiser limitar o número de operações assíncronas que estão sendo executadas em um determinado momento, poderá utilizar um semáforo (um System.Threading.SemaphoreSlim objeto). Você pode limitar para N o número de operações executadas simultaneamente inicializando a contagem do semáforo para N, aguardando no semáforo sempre que quiser executar uma operação e liberando o semáforo quando terminar uma operação:

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

Você também pode criar um semáforo assíncrono que não depende de alças de espera e, em vez disso, funciona completamente com tarefas. Para fazer isso, você pode usar técnicas como as discutidas em Consumindo o padrão assíncrono baseado em tarefas para criar estruturas de Taskdados sobre o .

Da TAP às alças de espera

Como mencionado anteriormente, a classe implementa IAsyncResultTask , e essa implementação expõe uma IAsyncResult.AsyncWaitHandle propriedade que retorna um identificador de espera que será definido quando o Task for concluído. Você pode obter um WaitHandle foro Task da seguinte maneira:

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

Consulte também