Delen via


Interoperabiliteit met andere asynchrone patronen en typen

Een korte geschiedenis van asynchrone patronen in .NET:

Taken en het Asynchrone programmeermodel (APM)

Van APM tot TAP

Omdat het Asynchrone programmeermodelpatroon (APM) gestructureerd is, is het vrij eenvoudig om een wrapper te bouwen om een APM-implementatie beschikbaar te maken als tap-implementatie. .NET Framework 4 en latere versies bevatten helperroutines in de vorm van overbelasting van FromAsync methoden om deze vertaling te bieden.

Houd rekening met de Stream klasse en EndReadBeginRead de bijbehorende methoden, die de APM-tegenhanger voor de synchrone methode vertegenwoordigenRead:

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

U kunt de TaskFactory<TResult>.FromAsync methode als volgt gebruiken om een TAP-wrapper te implementeren voor deze bewerking:

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

Deze implementatie is vergelijkbaar met de volgende:

 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

Van TAP naar APM

Als uw bestaande infrastructuur het APM-patroon verwacht, wilt u ook een TAP-implementatie gebruiken en deze gebruiken waar een APM-implementatie wordt verwacht. Omdat taken kunnen worden samengesteld en de Task klasse wordt geïmplementeerd IAsyncResult, kunt u hiervoor een eenvoudige helperfunctie gebruiken. De volgende code maakt gebruik van een uitbreiding van de Task<TResult> klasse, maar u kunt een bijna identieke functie gebruiken voor niet-algemene taken.

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

Overweeg nu een geval waarin u de volgende TAP-implementatie hebt:

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

en u wilt deze APM-implementatie opgeven:

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

In het volgende voorbeeld ziet u één migratie naar 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

Taken en het op gebeurtenissen gebaseerde Asynchrone patroon (EAP)

Het verpakken van een op gebeurtenissen gebaseerde EAP-implementatie (Asynchroon patroon) is meer betrokken dan het verpakken van een APM-patroon, omdat het EAP-patroon meer variatie en minder structuur heeft dan het APM-patroon. Om dit te demonstreren, verpakt de volgende code de DownloadStringAsync methode. DownloadStringAsync accepteert een URI, genereert de gebeurtenis tijdens het DownloadProgressChanged downloaden om meerdere statistieken over de voortgang te rapporteren en de gebeurtenis op te halen DownloadStringCompleted wanneer deze klaar is. Het uiteindelijke resultaat is een tekenreeks die de inhoud van de pagina op de opgegeven URI bevat.

 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

Taken en wachtgrepen

Van wachtgrepen tot TAP

Hoewel wachtgrepen geen asynchroon patroon implementeren, kunnen geavanceerde ontwikkelaars de WaitHandle klasse en de ThreadPool.RegisterWaitForSingleObject methode voor asynchrone meldingen gebruiken wanneer een wachtgreep is ingesteld. U kunt de RegisterWaitForSingleObject methode verpakken om een op taken gebaseerd alternatief in te schakelen voor een synchrone wachttijd op een wachtgreep:

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

Met deze methode kunt u bestaande WaitHandle implementaties gebruiken in asynchrone methoden. Als u bijvoorbeeld het aantal asynchrone bewerkingen wilt beperken dat op een bepaald moment wordt uitgevoerd, kunt u een semafore (een System.Threading.SemaphoreSlim object) gebruiken. U kunt beperken tot N het aantal bewerkingen dat gelijktijdig wordt uitgevoerd door het aantalemaforen te initialiseren naar N, te wachten op de semafore wanneer u een bewerking wilt uitvoeren en deemafore vrij te geven wanneer u klaar bent met een bewerking:

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

U kunt ook een asynchrone semafor maken die niet afhankelijk is van wachtgrepen en in plaats daarvan volledig werkt met taken. Hiervoor kunt u technieken gebruiken, zoals technieken die worden besproken in Het gebruik van het Asynchrone patroon op basis van taken voor het bouwen van Taskgegevensstructuren.

Van TAP tot wachtgrepen

Zoals eerder vermeld, implementeert IAsyncResultde Task klasse en die implementatie maakt een IAsyncResult.AsyncWaitHandle eigenschap beschikbaar die een wachtgreep retourneert die wordt ingesteld wanneer de Task uitvoering is voltooid. U kunt een WaitHandle voor een Task als volgt verkrijgen:

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

Zie ook