Interoperabiliteit met andere asynchrone patronen en typen
Een korte geschiedenis van asynchrone patronen in .NET:
- .NET Framework 1.0 heeft het IAsyncResult patroon geïntroduceerd, ook wel bekend als het Asynchrone programmeermodel (APM) of het
Begin/End
patroon. - .NET Framework 2.0 heeft het op gebeurtenissen gebaseerde Asynchrone patroon (EAP) toegevoegd.
- .NET Framework 4 heeft het op taken gebaseerde Asynchrone patroon (TAP) geïntroduceerd, dat zowel APM als EAP vervangt en de mogelijkheid biedt om eenvoudig migratieroutines te bouwen op basis van de eerdere patronen.
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