Interoperabilität mit anderen asynchronen Mustern und Typen
Eine kurze Geschichte der asynchronen Muster in .NET:
- Mit .NET Framework 1.0 wurde das Muster IAsyncResult eingeführt, das auch asynchrones Programmiermodell (APM) oder
Begin/End
-Muster genannt wird. - Mit .NET Framework 2.0 wurde das ereignisbasierte asynchrone Muster (Event-based Asynchronous Pattern, EAP) hinzugefügt.
- Mit .NET Framework 4 wurde das taskbasierte asynchrone Muster (Task-based Asynchronous Pattern, TAP) eingeführt, das sowohl das APM als auch das EAP ablöst und die Möglichkeit bietet, leicht Migrationsroutinen aus früheren Mustern zu erstellen.
Aufgaben und das asynchrone Programmiermodell (Asynchronous Programming Model, APM)
von APM zu TAP
Da das Muster Asynchrones Programmiermodell strukturiert ist, lässt sich ganz einfach ein Wrapper für eine APM-Implementierung erstellen, um sie als TAP-Implementierung verfügbar zu machen. .NET Framework 4 und höher enthält für diese Umwandlung Hilfsroutinen in Form von FromAsync-Methodenüberladungen.
Betrachten Sie die Stream -Klasse und ihre Methoden BeginRead / EndRead , die die APM-Entsprechung zur synchronen Read -Methode darstellen:
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
Sie können die TaskFactory<TResult>.FromAsync -Methode verwenden, um wie folgt einen TAP-Wrapper für diese Operation zu implementieren:
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
Diese Implementierung entspricht dem Folgenden:
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
von TAP zu APM
Wenn die vorhandene Infrastruktur das APM-Muster erwartet, sollten Sie auch eine TAP-Implementierung erstellen und diese verwenden, wo eine APM-Implementierung erwartet wird. Da Aufgaben erstellt werden können und die Task-Klasse IAsyncResult implementiert, können Sie hierfür eine einfache Hilfsfunktion verwenden. Der folgende Code verwendet eine Erweiterung der Task<TResult> -Klasse, aber Sie können eine fast identische Funktion für nicht generische Aufgaben verwenden.
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
Betrachten Sie nun einen Fall, bei dem Sie die folgende TAP-Implementierung haben:
public static Task<String> DownloadStringAsync(Uri url)
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)
und Sie möchten diese APM-Implementierung bereitstellen:
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
Im folgenden Beispiel wird eine Migration zu APM veranschaulicht:
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
Aufgaben und das ereignisbasierte asynchrone Muster (Event-based Asynchronous Pattern, EAP)
Das Umschließen einer Event-based Asynchronous Pattern (EAP) -Implementierung ist komplizierter als das Umschließen eines APM-Musters, da das EAP-Muster selbst mehr Varianten als das APM-Muster aufweist und geringer strukturiert ist. Zur Veranschaulichung umschließt der folgende Code die DownloadStringAsync
-Methode. DownloadStringAsync
akzeptiert einen URI, löst während des Herunterladens das DownloadProgressChanged
-Ereignis aus, um mehrere Fortschrittswerte zu berichten, und löst zum Abschluss das DownloadStringCompleted
-Ereignis aus. Das Endergebnis ist eine Zeichenfolge, die den Inhalt der Seite am angegebenen URI enthält.
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
Aufgaben und Wait-Handles
von den Wait-Handles zu TAP
Obwohl Wait-Handles kein asynchrones Muster implementieren, können erfahrene Entwickler die WaitHandle -Klasse und die ThreadPool.RegisterWaitForSingleObject -Methode für asynchrone Benachrichtigungen verwenden, wenn ein Wait-Handle festgelegt ist. Sie können die RegisterWaitForSingleObject -Methode umschließen, um eine aufgabenbasierte Alternative zu jedem synchronen Wartevorgang in einem Wait-Handle zu ermöglichen:
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
Mit dieser Methode können Sie vorhandene WaitHandle -Implementierungen in asynchronen Methoden verwenden. Wenn Sie beispielsweise die Anzahl von asynchronen Operationen beschränken möchten, die zu einem bestimmten Zeitpunkt ausgeführt werden, können Sie ein Semaphor ( System.Threading.SemaphoreSlim -Objekt) verwenden. Indem Sie den Zähler des Semaphors mit N initialisieren, vor jeder Ausführung eines Vorgangs auf das Semaphor warten und das Semaphor freigeben, wenn ein Vorgang abgeschlossen ist, können Sie die Anzahl von gleichzeitig ausgeführten Vorgängen auf N drosseln:
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
Sie können auch ein asynchrones Semaphor erstellen, das nicht auf Wait-Handles beruht und stattdessen vollständig mit Aufgaben funktioniert. Um dies zu erreichen, können Sie Techniken wie die diejenige verwenden, die unter Verwenden des aufgabenbasierten asynchronen Musters zum Erstellen von Datenstrukturen auf Taskhinzugefügt wurde.
von TAP zu Wait-Handles
Wie bereits erwähnt, implementiert die Task -Klasse IAsyncResult, und diese Implementierung macht die IAsyncResult.AsyncWaitHandle -Eigenschaft verfügbar, die ein Wait-Handle zurückgibt, das festgelegt wird, wenn der Task abgeschlossen wurde. Sie können ein WaitHandle für Task abrufen, wie folgt:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle