Взаимодействие с другими асинхронными шаблонами и типами
Краткая история асинхронных шаблонов в .NET
- В .NET Framework 1.0 появился шаблон IAsyncResult, также известный как асинхронная модель программирования (APM) или шаблон
Begin/End
. - В .NET Framework 2.0 была добавлена асинхронная модель на основе событий (EAP).
- В .NET Framework 4 появилась асинхронная модель на основе задач (TAP), которая заменила APM и EAP и предоставила возможность легко строить процедуры миграции с более ранних шаблонов.
Задачи и асинхронная модель программирования (APM)
от APM к TAP
Так как шаблон Асинхронная модель программирования (APM) структурирован, можно довольно легко создавать оболочки для предоставления реализации APM в качестве реализации TAP. Платформа .NET Framework 4 и более поздних версий включает вспомогательные процедуры в форме перегрузок методов FromAsync для реализации этого преобразования.
Рассмотрим класс Stream и его методы BeginRead и EndRead , которые представляют аналог APM для синхронного метода 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
Можно использовать метод TaskFactory<TResult>.FromAsync для реализации оболочки TAP этой операции следующим образом:
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
Эта реализация похожа на следующее:
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
от TAP к APM
Если существующая инфраструктура предполагает использование шаблона APM, также может потребоваться использовать реализацию TAP там, где ожидается реализация APM. Поскольку задачи можно создавать, а класс Task реализует интерфейс IAsyncResult, для этого можно использовать простую вспомогательную функцию. В следующем коде используется расширение класса Task<TResult> , но можно использовать почти такую же функцию для неуниверсальных задач.
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
Теперь рассмотрим случай, когда есть следующая реализация TAP:
public static Task<String> DownloadStringAsync(Uri url)
Public Shared Function DownloadStringAsync(url As Uri) As Task(Of String)
При этом необходимо предоставить следующую реализацию 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
В следующем примере показана одна миграция на 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
Задачи и асинхронная модель, основанная на событиях
Создание оболочки для реализации Event-based Asynchronous Pattern (EAP) сложнее создания оболочки для шаблона APM, так как шаблон EAP имеет несколько вариантов и менее структурирован, чем шаблон APM. В качестве демонстрации следующий код создает оболочку метода DownloadStringAsync
. DownloadStringAsync
принимает универсальный код ресурса (URI), создает событие DownloadProgressChanged
во время скачивания, чтобы периодически передавать данные о ходе загрузки, а когда загрузка завершена, создает событие DownloadStringCompleted
. Конечным результатом является строка, содержащая оглавление страницы по указанному URI.
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
Задачи и дескрипторы ожидания
от дескрипторов ожидания к TAP
Хотя дескрипторы ожидания не реализуют асинхронную модель, опытные разработчики могут использовать класс WaitHandle и метод ThreadPool.RegisterWaitForSingleObject для асинхронных уведомлений, когда задан дескриптор ожидания. Можно создать оболочку метода RegisterWaitForSingleObject для создания альтернативы синхронного ожидания на основе задач для дескриптора ожидания:
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
С помощью этого метода можно использовать существующие реализации WaitHandle в асинхронных методах. Например, если необходимо регулировать число асинхронных операций, которые выполняются в определенный момент времени, можно использовать семафор (объект System.Threading.SemaphoreSlim). Можно отрегулировать число операций, выполняемых параллельно, до N путем инициализации счетчика семафора равным 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
Можно также создать асинхронный семафор, который не зависит от дескрипторов ожидания и вместо этого работает только с задачами. Для этого используются методы, описанные в разделе Consuming the Task-based Asynchronous Pattern для построения структур данных на основе Task.
от TAP к дескрипторам ожидания
Как упоминалось ранее, класс Task реализует IAsyncResult, и эта реализация предоставляет свойство IAsyncResult.AsyncWaitHandle , которое возвращает дескриптор ожидания, задаваемое при завершении Task . Можно получить WaitHandle для Task следующим образом:
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle
См. также
- Task-based Asynchronous Pattern (TAP) (Асинхронный шаблон, основанный на задачах (TAP))
- Реализация асинхронной модели на основе задач
- Использование асинхронной модели на основе задач