Interop med andra asynkrona mönster och typer
En kort historik över asynkrona mönster i .NET:
- .NET Framework 1.0 introducerade IAsyncResult mönstret, även kallat Asynchronous Programming Model (APM) eller
Begin/End
mönstret. - .NET Framework 2.0 har lagt till det händelsebaserade Asynkrona mönstret (EAP).
- .NET Framework 4 introducerade det aktivitetsbaserade Asynkrona mönstret (TAP) som ersätter både APM och EAP och ger möjlighet att enkelt skapa migreringsrutiner från tidigare mönster.
Uppgifter och APM (Asynchronous Programming Model)
Från APM till TAP
Eftersom APM-mönstret (APM) är strukturerat är det ganska enkelt att skapa en wrapper för att exponera en APM-implementering som en TAP-implementering. .NET Framework 4 och senare versioner innehåller hjälprutiner i form av FromAsync metodöverbelastningar för att tillhandahålla den här översättningen.
Stream Tänk på klassen och dess BeginRead metoderEndRead, som representerar APM-motsvarigheten till den synkrona Read metoden:
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
Du kan använda TaskFactory<TResult>.FromAsync metoden för att implementera en TAP-omslutning för den här åtgärden på följande sätt:
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
Den här implementeringen liknar följande:
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
Från TAP till APM
Om din befintliga infrastruktur förväntar sig APM-mönstret vill du också implementera TAP och använda den där en APM-implementering förväntas. Eftersom aktiviteter kan skapas och Task klassen implementeras IAsyncResultkan du använda en enkel hjälpfunktion för att göra detta. Följande kod använder ett tillägg för Task<TResult> klassen, men du kan använda en nästan identisk funktion för icke-generiska uppgifter.
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
Nu bör du överväga ett fall där du har följande TAP-implementering:
public static Task<String> DownloadStringAsync(Uri url)
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)
och du vill tillhandahålla den här APM-implementeringen:
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
I följande exempel visas en migrering till 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
Uppgifter och det händelsebaserade Asynkrona mönstret (EAP)
Att omsluta en händelsebaserad EAP-implementering (Asynchronous Pattern) är mer involverat än att omsluta ett APM-mönster, eftersom EAP-mönstret har mer variation och mindre struktur än APM-mönstret. För att demonstrera omsluter DownloadStringAsync
följande kod metoden. DownloadStringAsync
accepterar en URI, genererar DownloadProgressChanged
händelsen vid nedladdning för att rapportera flera statistik om förloppet och genererar DownloadStringCompleted
händelsen när den är klar. Slutresultatet är en sträng som innehåller innehållet på sidan vid den angivna 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
Uppgifter och väntehandtag
Från Väntehandtag till TAP
Även om väntehandtag inte implementerar ett asynkront mönster kan avancerade utvecklare använda WaitHandle klassen och ThreadPool.RegisterWaitForSingleObject metoden för asynkrona meddelanden när ett väntehandtag har angetts. Du kan omsluta RegisterWaitForSingleObject metoden för att aktivera ett aktivitetsbaserat alternativ till synkron väntan på ett väntehandtag:
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
Med den här metoden kan du använda befintliga WaitHandle implementeringar i asynkrona metoder. Om du till exempel vill begränsa antalet asynkrona åtgärder som körs vid en viss tidpunkt kan du använda en semafor (ett System.Threading.SemaphoreSlim objekt). Du kan begränsa till N antalet åtgärder som körs samtidigt genom att initiera semaforantalet till N, vänta på semaforen när du vill utföra en åtgärd och släppa semaforen när du är klar med en åtgärd:
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
Du kan också skapa en asynkron semafor som inte förlitar sig på väntehandtag och i stället fungerar helt med uppgifter. För att göra detta kan du använda tekniker som de som beskrivs i Använda det aktivitetsbaserade asynkrona mönstret för att skapa datastrukturer ovanpå Task.
Från TAP till Väntehandtag
Som tidigare nämnts Task implementerar IAsyncResultklassen , och den implementeringen exponerar en IAsyncResult.AsyncWaitHandle egenskap som returnerar ett väntehandtag som kommer att anges när den Task är klar. Du kan få en WaitHandle för en Task enligt följande:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle