Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
Краткая история асинхронных шаблонов в .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
Задачи и асинхронный шаблон на основе событий (EAP)
Обёртывание реализации асинхронного шаблона, основанного на событиях (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, ожидая на семафоре всякий раз, когда хотите выполнить операцию, и освобождая семафор после завершения операции.
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
Вы также можете создать асинхронный семафор, который не зависит от дескрипторов ожидания и вместо этого полностью работает с задачами. Для этого можно использовать такие методы, как те, которые рассматриваются в статье "Использование асинхронного шаблона на основе задач " для создания структур данных поверх Task.
От TAP до дескрипторов ожидания
Как упоминалось ранее, класс Task реализует IAsyncResult, и эта реализация предоставляет свойство IAsyncResult.AsyncWaitHandle, которое возвращает дескриптор ожидания, который будет установлен после завершения Task. Вы можете получить WaitHandle для Task как указано ниже.
WaitHandle wh = ((IAsyncResult)task).AsyncWaitHandle;
Dim wh As WaitHandle = CType(task, IAsyncResult).AsyncWaitHandle