Nota
El acceso a esta página requiere autorización. Puede intentar iniciar sesión o cambiar directorios.
El acceso a esta página requiere autorización. Puede intentar cambiar los directorios.
Un breve historial de patrones asincrónicos en .NET:
- .NET Framework 1.0 introdujo el IAsyncResult patrón, también conocido como modelo de programación asincrónica (APM) o el
Begin/End
patrón . - .NET Framework 2.0 agregó el patrón asincrónico basado en eventos (EAP).
- .NET Framework 4 introdujo el patrón asincrónico basado en tareas (TAP), que sustituye a APM y EAP y proporciona la capacidad de crear fácilmente rutinas de migración a partir de los patrones anteriores.
Tareas y el modelo de programación asincrónica (APM)
De APM a TAP
Dado que el patrón del Modelo de Programación Asíncrona (APM) está estructurado, es bastante fácil crear un contenedor para exponer una implementación de APM como una implementación TAP. .NET Framework 4 y versiones posteriores incluyen rutinas auxiliares en forma de sobrecargas del método FromAsync para proporcionar esta traducción.
Tenga en cuenta la Stream clase y sus métodos BeginRead y EndRead que representan el homólogo de APM al método sincrónico 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
Puede usar el método TaskFactory<TResult>.FromAsync para implementar un contenedor TAP para esta operación de la siguiente manera:
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
Esta implementación es similar a la siguiente:
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
De TAP a APM
Si la infraestructura existente espera el patrón APM, también deseará realizar una implementación de TAP y usarla donde se espere una implementación de APM. Dado que las tareas se pueden componer y la Task clase implementa IAsyncResult, puede usar una función auxiliar sencilla para hacerlo. El código siguiente usa una extensión de la Task<TResult> clase , pero puede usar una función casi idéntica para tareas no genéricas.
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
Ahora, considere un caso en el que tenga la siguiente implementación de TAP:
public static Task<String> DownloadStringAsync(Uri url)
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)
y desea proporcionar esta implementación de 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
En el ejemplo siguiente se muestra una migración a 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
Tareas y el patrón asincrónico basado en eventos (EAP)
Ajustar una implementación de patrón asincrónico basado en eventos (EAP) es más implicada que ajustar un patrón de APM, ya que el patrón EAP tiene más variación y menos estructura que el patrón de APM. Para demostrarlo, el código siguiente encapsula el DownloadStringAsync
método .
DownloadStringAsync
acepta un URI, genera el DownloadProgressChanged
evento mientras se descarga para informar de varias estadísticas sobre el progreso y genera el DownloadStringCompleted
evento cuando haya terminado. El resultado final es una cadena que contiene el contenido de la página en el URI especificado.
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
Tareas y controladores de espera
de controladores de espera a TAP
Aunque los identificadores de espera no implementan un patrón asincrónico, los desarrolladores avanzados pueden usar la WaitHandle clase y el ThreadPool.RegisterWaitForSingleObject método para las notificaciones asincrónicas cuando se establece un identificador de espera. Puede encapsular el método RegisterWaitForSingleObject para habilitar una alternativa basada en tareas para cualquier espera sincrónica en un controlador de espera:
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 este método, puede usar implementaciones existentes WaitHandle en métodos asincrónicos. Por ejemplo, si desea limitar el número de operaciones asincrónicas que se ejecutan en cualquier momento determinado, puede usar un semáforo (un System.Threading.SemaphoreSlim objeto). Puede limitar a N el número de operaciones que se ejecutan simultáneamente inicializando el recuento del semáforo en N, esperando en el semáforo cada vez que desee realizar una operación y liberando el semáforo cuando haya terminado con una operación:
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
También se puede compilar un semáforo asincrónico que no se base en controladores de espera, sino que funcione completamente con tareas. Para ello, puede usar técnicas como las descritas en Uso del patrón asincrónico basado en tareas para crear estructuras de datos sobre Task.
de TAP a controladores de espera
Como se mencionó anteriormente, la Task clase implementa IAsyncResulty esa implementación expone una IAsyncResult.AsyncWaitHandle propiedad que devuelve un identificador de espera que se establecerá cuando Task se complete. Puede obtener un WaitHandle para un Task de la siguiente manera:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle