Паттерны асинхронного программирования в .NET
Большинство современных приложений устроены так, что им необходимо постоянное взаимодействие с миром: получение данных из БД, отправка запросов на внешний web-ресурс, ожидание ввода пользователя.
Наиболее привычный синхронный вызов таких взаимодействий приводит к простаиванию потоков в ожидании ответов, к избыточному расходованию оперативной памяти (потоки впустую занимают память). Все это является причиной снижения производительности приложения, а также его невысокой способности к масштабированию.
Запросы к веб-сервисам и к внешним ресурсам (такие как, базы данных), запросы, интенсивно использующие I/O-операции - хорошей практикой в описанных случаях является использование шаблонов асинхронного программирования - способа выполнения длительных операций без блокировки вызывающего потока.
Выделают следующие паттерны асинхронного программирования:
- асинхронный шаблон или Asynchronous Programming Model (APM);
- асинхронный шаблон, основанный на событиях, или Event-based Asynchronous Pattern (EAP);
- асинхронный шаблон, основанный на задачах, или Task-based Asynchronous Pattern (TAP).
В .NET модель AРМ появилась еще в первой версии фреймворка .NET. В .NET Framework 2.0 появилась модель EAP. TAP-паттерн базируется на типе Task, появившемся в .NET 4.0, и применении ключевых слов async и await, появившихся в компиляторе C# версии 5.
В API следующих классов есть поддержка вызовов асинхронных методов (доступно в .NET Framework 4.5):
- работа c web-ресурсами: System.Net.Http.HttpClient, System.Net.WebRequest, System.Net.Sockets.Socket, System.Net.Dns, etc.;
- работа с web-сервисами: инструменты генерации прокси для веб-сервисов (wsdl.exe и svcutil.exe) генерируют код вызова методов служб в соответствии с паттернами APM, EAP, TAP;
- работа с файловой системой: StorageFile, StreamWriter, StreamReader, XmlReader;
- работа с базами данных: System.Data.SqlClient.SqlCommand;
- работа с графикой: MediaCapture, BitmapEncoder, BitmapDecoder.
Ниже обзорно рассмотрен каждый из паттернов асинхронного программирования, а также приведены примеры вызовов WCF-служб с использованием каждого из перечисленных шаблонов.
Введем некоторый класс, имеющий метод, описанных следующим псевдокодом:
public TResult {MethodName}(TIn[] args) { … }
И сервис MessageService:
public string SendMessage(string userName, string message)
{
Contract.Requires<ArgumentNullException>(userName != null);
Contract.Requires<ArgumentNullException>(message != null);
Thread.Sleep(2000); // немного сна на рабочем месте)
return String.Format("{0} send '{1}'", userName, message);
}
Синхронный вызов MessageService выглядит так:
using (var client = new MessageServiceClient())
{
try
{
var actual = client.SendMessage("@codezombi", "Send sync");
client.Close();
Console.WriteLine(actual);
}
finally
{
if (client.State != CommunicationState.Closed)
client.Abort();
}
}
И возвращает следующий вывод:
Start main()
@codezombi send 'Send sync'
Time elapsed: 00:00:02.3077486
End main()
Asynchronous Programming Model (APM)
Asynchronous Programming Model (APM) - модель асинхронного программирования, основанная на следующем шаблоне:
public IAsyncResult Begin{MethodName}(TIn[] args, AsyncCallback callback, object userState = null) { … }
public TResult End{MethodName}(IAsyncResult result) { ... }
где
Begin{MethodName}:
начинает асинхронное выполнение операции {MethodName},
принимает параметры args, совпадающие с сигнатурой синхронного метода; делегат System.AsyncCalback, указывающий метод, вызываемый при
завершении асинхронной операции; и объект состояния userState, содержащий дополнительные сведения об асинхронной операции,
и возвращает объект, реализующий интерфейс System.IAsyncResult.
End{MethodName}, соответственно:
заканчивает асинхронное выполнение операции;
принимает объект, реализующий IAsyncResult, и возвращает с типом TResult, совпадающим с синхронной версией метода {MethodName}.
Вызов веб-службы для паттерна APM будет выглядеть так:
static void SendMessageAsyncAPM()
{
var client = new MessageServiceClient();
client.BeginSendMessage("@codezombi", "Send async [APM]",
ar =>
{
var c = (MessageServiceClient)ar.AsyncState;
var actual = c.EndSendMessage(ar);
Console.WriteLine(actual);
},
client);
}
Имеем следующий вывод:
Start main()
Invoked: 00:00:00.2706677
End main()
Callback: 00:00:02.3433612
@codezombi send 'Send async [APM]'
У модели APM есть следующие недостатки:
- необходимость написания кода обратного вызова для всех асинхронных операций;
- невозможность использования «привычного» синтаксиса, такого как: использование конструкции using… finally для открытия и закрытия канала;
- сложность реализации отмены операции, реакции на таймаут ответа сервиса.
Event-based Asynchronous Pattern (EAP)
Event-based Asynchronous Pattern (EAP) - асинхронный шаблон, основанный на событиях и описываемый в простейшем случае следующим псевдокодом:
public void {MethodName}Async(TInput[] args, object userState = null)
{
InvokeAsync(onBegin{MethodName}Delegate, args, onEnd{MethodName}Delegate, on{MethodName}CompletedDelegate, userState);
}
EventHandler<{MethodName}CompletedEventArgs> {MethodName}Completed;
private void On{MethodName}Completed(object state) { … }
/// <param name="beginOperationDelegate">Делегат, используемые для вызова асинхронной операции</param>
/// <param name="inValues">Входные значения вызова</param>
/// <param name="endOperationDelegate">Делегат, используемые для окончания асинхронного вызова</param>
/// <param name="operationCompletedCallback">Обратный вызов (также делегат), вызываемый, когда асинхронный метод выполнится.
Передается в <see cref="T:System.ServiceModel.ClientBase`1.BeginOperationDelegate"/>.</param>
/// <param name="userState">Объект состояния, ассоциированный с асинхронным вызовом</param>
void InvokeAsync(BeginOperationDelegate beginOperationDelegate, object[] inValues, EndOperationDelegate endOperationDelegate,
SendOrPostCallback operationCompletedCallback, object userState);
где
{MethodName}Async() начинает асинхронное выполнение операции {MethodName}; принимает параметры args, совпадающие с сигнатурой синхронного метода,
и необязательный параметр userState, содержащий дополнительные сведения об асинхронной операции.
{MethodName}Completed - событие, наступающее, когда асинхронный метод выполнился.
Вызов веб-службы для паттерна EAP:
static void SendMessageAsyncEAP()
{
var client = new MessageServiceClient();
client.SendMessageCompleted += (sender, args) =>
{
var actual = args.Result;
Console.WriteLine(actual);
};
client.SendMessageAsync("@codezombi", "Send async [EAP]");
}
Вывод:
Start main()
Invoked: 00:00:00.5665485
End main()
Callback: 00:00:02.6351425
@codezombi send 'Send async [EAP]'
Использовать EAP или APM при написании асинхронного кода – в целом, дела вкуса. EAP не решает ни одной из проблем свойственных шаблону APM. А именно:
- для вызова для всех асинхронных операций все также необходимо писать дополнительный код (хотя уже и нет необходимости в введении дополнительных аргументов asyncCallaback и userState);
- невозможно использовать конструкции using… finally для открытия и закрытия канала;
- сложность реализации отмены операции, перехвата таймаута ответа сервиса.
Task-based Asynchronous Pattern (TAP)
Task-based Asynchronous Pattern (TAP) - асинхронный шаблон, основанный на задачах. В .NET шаблон TAP базируется на классах Task и Task<T>, а также на ключевых словах C#: async и await.
Паттерн TAP является рекомендуемым шаблоном разработки в .NET [1].
Паттерн TAP является более изящным по сравнению с APM и EAP и не требует написания/генерации дополнительных методов с префиксами Begin/End или постфиксом Async.
Вызов веб-службы для паттерна TAP:
static async Task<string> SendMessageAsync()
{
var client = new MessageServiceClient();
var task = Task.Factory.StartNew(() => client.SendMessage("@codezombi", "Send async [TAP]"));
var actual = await task;
return actual;
}
Вывод:
Start main()
Elapsed time: 00:00:02.2998571
@codezombi send 'Send async [TAP]'
End main()
TAP. Yet another Implementation
И последний пример вызова сервиса, который будет показан, основан на «task-based»-режиме генерации WCF-прокси.
Этот вызов является частным случаем уже обсужденного паттерна TAP, и имеет, пожалуй, самый изящный код вызова.
Вызов «task-based»-операции веб-службы:
static async Task<string> SendMessageAsyncNew()
{
var client = new MessageTaskServiceClient();
var actual = await client.SendMessageAsync("@codezombi", "Send async [TAP]");
return actual;
}
Вывод:
Start main()
End main()
Elapsed time: 00:00:02.2863746
@codezombi send 'Send async [TAP]'
Дополнительные источники
- [1] Asynchronous Programming Pattern, MSDN.
- [2] Designing Service Contracts: Synchronous and Asynchronous Operations. MSDN.
- [3] Stephen Toub. The Task-based Asynchronous Pattern, Microsoft. - 2012.
- [4] Alex Daviess. Async in C# 5.0, O'Reilly. - 2012.
Only figuring out the benefit of gambling web sites is obviously not sufficient. Most of us usually are not even aware of the fact that|the reality that} it takes less thecasinosource.com of time to create an internet slot and the same factor is much cheaper than making slots at land-based casinos. The same level has encouraged software program providers corresponding to Microgaming, Net Entertainment, and Playtech to create new slots each month.
ОтветитьУдалить