Observação
O acesso a essa página exige autorização. Você pode tentar entrar ou alterar diretórios.
O acesso a essa página exige autorização. Você pode tentar alterar os diretórios.
Um breve histórico de padrões assíncronos no .NET:
- O .NET Framework 1.0 introduziu o IAsyncResult padrão, também conhecido como APM (Modelo de Programação Assíncrono) ou o
Begin/Endpadrão. - O .NET Framework 2.0 adicionou o EAP (Padrão Assíncrono) baseado em evento.
- O .NET Framework 4 introduziu o TAP (Padrão Assíncrono baseado em tarefa), que substitui o APM e o EAP e fornece a capacidade de criar facilmente rotinas de migração dos padrões anteriores.
Tarefas e o modelo de programação assíncrona (APM)
Do APM ao TAP
Como o padrão APM (Modelo de Programação Assíncrona) é estruturado, é muito fácil criar um wrapper para expor uma implementação do APM como uma implementação do TAP. O .NET Framework 4 e versões posteriores incluem funções auxiliares na forma de sobrecargas do método FromAsync para fornecer essa tradução.
Considere a Stream classe e seus métodos BeginRead e EndRead, que representam o equivalente APM ao método síncrono 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
Você pode usar o TaskFactory<TResult>.FromAsync método para implementar um wrapper TAP para esta operação da seguinte maneira:
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
Essa implementação é semelhante à seguinte:
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
Do TAP ao APM
Se a infraestrutura existente esperar o padrão APM, convém também pegar uma implementação do TAP e usá-la onde uma implementação do APM é esperada. Como as tarefas podem ser compostas e a classe Task implementa IAsyncResult, você pode usar uma função auxiliar simples para fazer isso. O código a seguir usa uma extensão da Task<TResult> classe, mas você pode usar uma função quase idêntica para tarefas não 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
Agora, considere um caso em que você tenha a seguinte implementação de TAP:
public static Task<String> DownloadStringAsync(Uri url)
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)
e você deseja fornecer esta implementação do 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
O exemplo a seguir demonstra uma migração para o 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
Tarefas e o padrão assíncrono baseado em evento (EAP)
Encapsular uma implementação de EAP (Padrão Assíncrono) baseado em evento é mais envolvido do que encapsular um padrão APM, pois o padrão EAP tem mais variação e menos estrutura do que o padrão APM. Para demonstrar, o código a seguir encapsula o DownloadStringAsync método. DownloadStringAsync aceita um URI, gera o evento DownloadProgressChanged ao fazer o download para relatar várias estatísticas sobre o progresso e gera o evento DownloadStringCompleted quando conclui. O resultado final é uma cadeia de caracteres que contém o conteúdo da página no 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
Tarefas e mecanismos de espera
De Identificadores de espera para TAP
Embora os identificadores de espera não implementem um padrão assíncrono, os desenvolvedores avançados podem usar a WaitHandle classe e o ThreadPool.RegisterWaitForSingleObject método para notificações assíncronas quando um identificador de espera é definido. Você pode dispor o método RegisterWaitForSingleObject para habilitar uma alternativa baseada em tarefa para qualquer espera síncrona em um identificador 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
Com esse método, você pode usar implementações existentes WaitHandle em métodos assíncronos. Por exemplo, se você quiser limitar o número de operações assíncronas que estão sendo executadas a qualquer momento específico, poderá utilizar um semáforo (um System.Threading.SemaphoreSlim objeto). Você pode limitar a N o número de operações que são executadas simultaneamente inicializando a contagem do semáforo para N, aguardando o semáforo sempre que quiser executar uma operação e liberando o semáforo quando terminar uma operação:
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
Você também pode criar um semáforo assíncrono que não depende de identificadores de espera e só trabalha com tarefas. Para fazer isso, você pode usar técnicas como as discutidas em Consuming the Task-based Asynchronous Pattern para construir estruturas de dados sobre Task.
De TAP para Identificadores de espera
Conforme mencionado anteriormente, a classe Task implementa o IAsyncResult e essa implementação expõe uma propriedade IAsyncResult.AsyncWaitHandle que retorna um identificador de espera que será definido quando Task for concluído. Você pode obter um WaitHandle para um Task da seguinte maneira:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle