Megosztás a következőn keresztül:


Együttműködés más aszinkron mintákkal és típusokkal

A .NET aszinkron mintáinak rövid története:

Feladatok és aszinkron programozási modell (APM)

Az APM-ről a TAP-ra

Mivel az aszinkron programozási modell (APM) mintája strukturált, elég könnyű burkolót létrehozni, hogy az APM-implementációt TAP-implementációként tegye elérhetővé. A .NET-keretrendszer 4- és újabb verziói a fordítás biztosításához metódustúlterheltség formájában FromAsync tartalmaznak segéd rutinokat.

Vegye figyelembe az Stream osztályt és annak BeginRead és EndRead módszereit, amelyek a szinkron metódus APM-megfelelőjét képviselik 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

A TaskFactory<TResult>.FromAsync metódust az alábbiak szerint implementálhat egy TAP wrappert ehhez a művelethez:

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

Ez a megvalósítás a következőhöz hasonló:

 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

A TAP-tól az APM-ig

Ha a meglévő infrastruktúra elvárja az APM mintát, akkor érdemes egy TAP-implementációt is alkalmazni ott, ahol egy APM-implementációra van szükség. Mivel a feladatok összeállíthatók és az Task osztály implementálja a IAsyncResult-t, ehhez használhat egy egyszerű segédfüggvényt. Az alábbi kód az Task<TResult> osztály egy bővítményét használja, de szinte azonos függvényt használhat nem általános feladatokhoz.

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

Most fontolja meg azt az esetet, amikor a következő TAP-implementációval rendelkezik:

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

és biztosítani szeretné ezt az APM-implementációt:

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

Az alábbi példa egy áttelepítést mutat be az APM-be:

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

Feladatok és az eseményalapú aszinkron minta (EAP)

Az eseményalapú aszinkron minta (EAP) implementációjának burkolása nagyobb szerepet játszik, mint egy APM-minta körbefuttatása, mivel az EAP-minta több variációval és kevesebb struktúrával rendelkezik, mint az APM-minta. A szemléltetés érdekében a következő kód körbefuttatja a metódust DownloadStringAsync . DownloadStringAsync elfogad egy URI-t, letöltés közben elindítja a DownloadProgressChanged eseményt, hogy több statisztikát jelentsen az előrehaladásról, és az DownloadStringCompleted eseményt indítja el, amikor a letöltés befejeződik. A végeredmény egy sztring, amely a lap tartalmát tartalmazza a megadott URI-n.

 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

Feladatok és várakozáskezelők

Várakozási fogópontoktól a TAP-ig

Bár a várakozási fogópontok nem implementálnak aszinkron mintát, a haladó fejlesztők használhatják az WaitHandle osztályt és a ThreadPool.RegisterWaitForSingleObject metódust az aszinkron értesítésekhez a várakozási fogópont beállításakor. A metódust körbecsomagolva engedélyezheti a RegisterWaitForSingleObject esetében a feladatalapú megközelítést, szemben a várakozási fogóponton végzett szinkron várakozással.

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

Ezzel a módszerrel a meglévő WaitHandle implementációkat aszinkron metódusokban használhatja. Ha például egy adott időpontban végrehajtott aszinkron műveletek számát szeretné szabályozni, használhat szemaphore-t (objektumot System.Threading.SemaphoreSlim ). Az egyidejűleg futó műveletek számát N-re korlátozhatja, ha a szemafor számát N-re inicializálja, a szemaforra vár, amikor egy műveletet végre szeretne hajtani, és felszabadítja a szemafort, amikor végzett egy művelettel.

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

Olyan aszinkron szemafor is létrehozható, amely nem támaszkodik a várakozási fogópontokra, hanem teljesen a feladatokkal működik. Ehhez használhat olyan technikákat, mint amilyenek a Feladatalapú Aszinkron Minta keretében tárgyalva vannak, hogy adatstruktúrákat építsen a Task tetejére.

A TAP-tól a várakozási fogópontokig

Ahogy korábban említettük, a Task osztály implementálja a IAsyncResult-t, és az implementáció egy olyan IAsyncResult.AsyncWaitHandle tulajdonságot tesz elérhetővé, amely egy várakozási fogópontot ad vissza, ami akkor lesz beállítva, amikor a Task befejeződik. A WaitHandle érdekében a következőképpen juthat hozzá egy Task-hez:

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

Lásd még