Nota
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare ad accedere o modificare le directory.
L'accesso a questa pagina richiede l'autorizzazione. È possibile provare a modificare le directory.
Breve cronologia dei modelli asincroni in .NET:
- .NET Framework 1.0 ha introdotto il IAsyncResult modello, altrimenti noto come modello di programmazione asincrona (APM) o il
Begin/End
modello. - .NET Framework 2.0 ha aggiunto il modello asincrono basato su eventi (EAP).
- .NET Framework 4 ha introdotto il modello asincrono basato su attività (TAP), che sostituisce sia APM che EAP e offre la possibilità di compilare facilmente routine di migrazione dai modelli precedenti.
Attività e modello di programmazione asincrona
Da APM a TAP
Poiché il modello APM (Asynchronous Programming Model) è strutturato, è piuttosto facile creare un wrapper per esporre un'implementazione di APM come implementazione TAP. .NET Framework 4 e versioni successive includono routine helper sotto forma di overload di metodo FromAsync per fornire questa traduzione.
Si consideri la classe Stream e i metodi BeginRead e EndRead che rappresentano la controparte APM del metodo sincrono 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
È possibile usare il TaskFactory<TResult>.FromAsync metodo per implementare un wrapper TAP per questa operazione come indicato di seguito:
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
Questa implementazione è simile alla seguente:
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
Da TAP a APM
Se l'infrastruttura esistente prevede il modello APM, è anche necessario adottare un'implementazione TAP e usarla dove è prevista un'implementazione di APM. Poiché le attività possono essere composte e la Task classe implementa IAsyncResult, è possibile usare una funzione helper semplice per eseguire questa operazione. Il codice seguente usa un'estensione della Task<TResult> classe , ma è possibile usare una funzione quasi identica per le attività non generiche.
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
Si consideri ora un caso in cui si dispone dell'implementazione TAP seguente:
public static Task<String> DownloadStringAsync(Uri url)
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)
e si vuole fornire questa implementazione di APM:
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
L'esempio seguente illustra una migrazione ad 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
Attività e modello asincrono basato su eventi
Il processo di incapsulamento di un'implementazione del modello asincrono basato su eventi è più complesso rispetto all'incapsulamento di un modello APM, perché il modello EAP presenta più variazioni e una struttura meno definita rispetto al modello APM. Per dimostrare, il codice seguente incapsula il metodo DownloadStringAsync
.
DownloadStringAsync
accetta un URI, genera l'evento durante il DownloadProgressChanged
download per segnalare più statistiche sullo stato di avanzamento e genera l'evento DownloadStringCompleted
al termine. Il risultato finale è una stringa che contiene il contenuto della pagina in corrispondenza dell'URI specificato.
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
Attività e handle di attesa
Da Handle di attesa a TAP
Anche se gli handle di attesa non implementano un modello asincrono, gli sviluppatori avanzati possono usare la WaitHandle classe e il ThreadPool.RegisterWaitForSingleObject metodo per le notifiche asincrone quando viene impostato un handle di attesa. È possibile incapsulare il metodo RegisterWaitForSingleObject per abilitare un'alternativa basata su attività a qualsiasi attesa sincrona su un handle di attesa:
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
Con questo metodo, è possibile usare le implementazioni esistenti WaitHandle nei metodi asincroni. Ad esempio, se si desidera limitare il numero di operazioni asincrone in esecuzione in un determinato momento, è possibile utilizzare un semaforo (oggetto System.Threading.SemaphoreSlim ). È possibile limitare a N il numero di operazioni eseguite simultaneamente inizializzando il conteggio del semaforo su N, in attesa del semaforo ogni volta che si vuole eseguire un'operazione e rilasciando il semaforo al termine di un'operazione:
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
È anche possibile creare un semaforo asincrono che non si basa su handle di attesa e funziona invece completamente con le attività. A tale scopo, è possibile usare tecniche come quelle descritte in Utilizzo del modello asincrono basato su attività per la creazione di strutture di dati su Task.
Da TAP a Handle di attesa
Come accennato in precedenza, la Task classe implementa IAsyncResulte l'implementazione espone una IAsyncResult.AsyncWaitHandle proprietà che restituisce un handle di attesa che verrà impostato al termine dell'operazione Task . È possibile ottenere un WaitHandle per un Task come segue:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle