Interoperabilita s jinými asynchronními vzory a typy

Stručná historie asynchronních vzorů v .NET:

Úlohy a asynchronní programovací model (APM)

Od APM do TAP

Vzhledem k tomu, že vzor asynchronního programovacího modelu (APM) je strukturovaný, je poměrně snadné vytvořit obálku pro zveřejnění implementace APM jako implementace TAP. Rozhraní .NET Framework 4 a novější verze obsahují pomocné rutiny ve formě FromAsync přetížení metod, které tento překlad poskytují.

Vezměte v úvahu Stream třídu a její BeginRead a EndRead metody, které představují protějšk APM synchronní Read metody:

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

Metodu TaskFactory<TResult>.FromAsync můžete použít k implementaci obálky TAP pro tuto operaci následujícím způsobem:

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

Tato implementace je podobná následující:

 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 TAP do APM

Pokud vaše stávající infrastruktura očekává vzor APM, budete také chtít provést implementaci TAP a použít ji tam, kde se očekává implementace APM. Vzhledem k tomu, že úkoly lze vytvářet a třída Task implementuje třídu IAsyncResult, můžete použít jednoduchou pomocnou funkci. Následující kód používá rozšíření Task<TResult> třídy, ale můžete použít téměř identickou funkci pro ne generické úlohy.

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

Teď zvažte případ, kdy máte následující implementaci TAP:

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

a chcete poskytnout tuto implementaci 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

Následující příklad ukazuje jednu migraci na 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

Úlohy a asynchronní vzor založený na událostech (EAP)

Zabalení implementace asynchronního vzoru založeného na událostech (EAP) je více zapojeno než zabalení vzoru APM, protože vzor EAP má více variant a menší strukturu než model APM. Pro předvedení následující kód zabalí metodu DownloadStringAsync . DownloadStringAsync přijímá identifikátor URI, vyvolá DownloadProgressChanged událost při stahování, aby se hlásilo více statistik o průběhu, a DownloadStringCompleted vyvolá událost po dokončení. Konečný výsledek je řetězec, který obsahuje obsah stránky na zadaném identifikátoru 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

Úlohy a obslužné rutiny čekání

Od popisovačů čekání až po KLEPNUTÍ

I když obslužné rutiny čekání neimplementují asynchronní vzor, můžou pokročilí vývojáři použít WaitHandle třídu a metodu ThreadPool.RegisterWaitForSingleObject pro asynchronní oznámení, když je nastavený popisovač čekání. Můžete zabalit metodu RegisterWaitForSingleObject , která povolí alternativu založenou na úlohách k jakékoli synchronní čekání na obslužné rutině čekání:

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

Pomocí této metody můžete použít existující WaitHandle implementace v asynchronních metodách. Pokud například chcete omezovat počet asynchronních operací, které se v určitém okamžiku spouští, můžete použít semafor ( System.Threading.SemaphoreSlim objekt). Počet operací, které se spouští souběžně, můžete omezovat na N tím, že inicializujete počet semaphore na N, kdykoli budete chtít provést operaci, a uvolněním semaforu po dokončení operace:

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

Můžete také vytvořit asynchronní semaphore, který nespoléhá na obslužné rutiny čekání a místo toho funguje zcela s úlohami. K tomu můžete použít techniky, jako jsou ty, které jsou popsány v používání asynchronního vzoru založeného na úlohách pro vytváření datových struktur nad Task.

Z TAP na obslužné úchyty čekání

Jak jsme již zmínili, Task třída implementuje IAsyncResulta tato implementace zveřejňuje IAsyncResult.AsyncWaitHandle vlastnost, která vrací popisovač čekání, který bude nastaven po dokončení Task . Můžete získat WaitHandle následující Task :

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

Viz také