Асинхронное программирование в C sharp. Вступление.

08.10.2018

Многие слышали об этом, но не многие используют его в своем коде. Между тем, никакие более-менее серьезные программы с клиент-серверной архитектурой не обойдутся без асинхронного программирования. Обмен данными с базой, взаимодействие клиента и сервера - это требует времени, которое можно занять другими процессами вместо ожидания.

При синхронном выполнения операции, поток блокируется другим. И приходится ждать выполнения этого второго процесса, чтобы вернуть управление первому. Это вызывает не нужную трату ресурсов, ведь поток с единственной задачей может достаточно долго ожидать ответ. От базы данных, например, или веб-сервиса.

И если временные ресурсы может сберечь многопоточность (благо современные процессоры это позволяют), то ресурсы памяти это не спасет. Ведь по сути, многопоточность - это тоже синхронное выполнение операций. Просто их несколько.

Настоящим выходом будет использование асинхронной обработки. При таком подходе, мы можем использовать несколько потоков передающих управление то одной, то другой задаче.

То есть, ожидая ответа от другой задачи, текущая не блокирует поток, а предоставляет его другой задаче.

Давайте посмотрим на рисунок:

В этом случае используется однопоточный асинхронный подход.

А здесь многопоточный асинхронный поток.

Каждый поток выполняет множество задач. Когда одна из задач останавливается в ожидании, берется другая. Таким образом задачи перетекают от одного потока к другому, в зависимости от того, который освободился первым. Из рисунка видно, что Task 1 начала выполняться в первом потоке, а закончила во втором.

Рассмотрим еще один рисунок - диаграмма последовательности:

Диаграмма описывает поведение потоков для клиент-серверного приложения. Клиент отправляет запрос данных от сервера, и, вместо того чтобы "зависнуть" ожидая ответ (как в синхронном подходе) продолжает работать, предоставляя пользователю другой функционал приложения. Так что, если вы веб разработчик, то без асинхронности - никуда.

Давайте немного разберемся в теории. Всего можно выделить три шаблона асинхронного программирования:

  • Asynchronous Programming Model (APM);
  • Event-based Asynchronous Pattern (EAP);
  • Task-based Asynchronous Pattern(TAP).

Asynchronous Programming Model появился еще в первой версии .Net Framework. APM позволил создавать асинхронные версии синхронных методов посредством двух методов - Begin MethodName и End MethodName.

Итак, всего два метода:

1
2
3
4
public IAsyncResult Begin{MethodName}(TIn[] args, AsyncCallback callback, object userState =null) 
{ 
... 
}

И:

1
2
3
4
public TResult End{MethodName}(IAsyncResult result) 
{ 
... 
}

Метод Begin{MethodName} начинает асинхронную операцию. Он принимает параметры args, callback - делегат на метод, вызываемый после выполнения асинхронного метода, объект userState, который используется для передачи информации о состоянии конкретного приложения в метод, вызываемый при завершении асинхронной операции.

Метод возвращает объект типа IAsyncResult который хранит информацию об асинхронной операции.

Метод End{MethodName} завершает асинхронную операцию. Он принимает на вход объект, типа IAsyncResult, а возвращает TResult, который на самом деле вернет тип, определенный в синхронной копии этого метода.

Давайте посмотрим, как используется этот шаблон на упрощенном примере:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public void Button_Click (...)
{
WebRequest request = WebRequest.Create(url);
request.BeginGetResponse(Callback , request);
}

public void Callback(IAsyncResult ar)
{
WebRequest request = (WebRequest) ar.AsyncState;
try
{
var response = request.EndGetResponse(ar); // Code does something with successful response
}
catch (WebException e)
{
// Error handling code
}
}

Мы вызвали метод Begin в обработчике события нажатия кнопки. В качестве параметра передаем callback в этот метод. И, уже в самом callback-е вызываем парный метод - End.

К недостаткам асинхронной модели программирования можно отнести:

  • Необходимо создавать функцию обратного вызова;
  • Нет возможности прервать операцию.
  • Нет возможности отказаться от вызова метода обратного вызова, если он был передан при начале операции.
  • Нет оповещения о прогрессе операции или о промежуточных результатах.

Event-based Asynchronous Pattern

Этот шаблон асинхронного программирования появился во второй версии фреймворка .Net. Он основан на событиях и асинхронных методах. Класс, реализующий этот шаблон будет содержать методы MethodNameAsync и MethodNameAsyncCancel (если нужна обработка отмены операции), и событие MethodNameCompleted. В этом же классе можно разместить синхронные версии методов, работающие с тем же потоком. Чаще всего этот шаблон используют при работе с веб сервисами. Например, ajax реализует Event-based Asynchronous Pattern. Получить результат асинхронной операции и обработать ошибки можно только в обработчике события MethodNameCompleted.

Шаблон асинхронного программирования, основанный на событиях решил некоторые проблемы предшественника:

  • Объявление метода для получения результата асинхронной операции;
  • Не предусмотрен механизм оповещения о прогрессе операции.

Однако, данный шаблон все равно имеет ряд недостатков:

  • Нет возможности передать контекст вызова операции (пользовательские данные) в метод обработки результата;
  • Не все операции можно прерывать. Методы, поддерживающие только одну операцию в очереди невозможно прервать;
  • Невозможно задать, в контексте какого потока будут вызываться методы обратного вызова.

Task-based Asynchronous Pattern (TAP)

Третий шаблон асинхронного программирования появился в .Net Framework 4.0. Из названия понятно, что он базируется на использовании задач. Основа TAP - два типа System.Threading.Tasks.Task и System.Threading.Tasks.Task TResult .

TAP позволяет разработчикам определять асинхронные функции в рамках одного метода. Теперь нет необходимости создавать функции начала и конца асинхронной операции, а потом еще и обратного вызова. Это конечно облегчает труд программиста, уменьшает порог вхождения в технологию, и просто делает программирование приятным.

TAP использует задачи для выполнения операций. Для каждой задачи используется отдельный поток, который берется из пула потоков. После выполнения задачи, поток возвращается в пул.

Модификатор "async" - этот модификатор применяется к методу или лямбда-выражению, или анонимному методу - он указывает, что метод является асинхронным и сигнализирует о возможности одного или нескольких вхождений оператора ожидания в этот метод.

Давайте посмотрим на пример определения метода:

1
2
3
4
5
6
public async Task< int >MyProcessAsync()
{
...
Var Overtime = await new ERP().ProcessOvertime(emp);
...
}

Обратите внимание на ключевые слова async и await. Именно эти операторы сигнализируют, что используется Task-based Asynchronous Pattern. Модификатор async указывает, что метод асинхронный. А оператор await может быть вызван внутри метода один или несколько раз. Он приостанавливает выполнение задачи до получения результата, в то время как поток продолжает свою работу.

А вот пример исползования TAP из жизни. Вызов веб службы:

1
2
3
4
5
6
7
static async Task< string > SendMessageAsync()
{
var client = new MyServiceClient();
var task = Task.Factory.StartNew(() = > client.SendMessageAsync("Message"));
var result = await task;
return result;
}

А вот другой способ вызова, еще более простой и понятный:

1
2
3
4
5
6
static async Task< string > SendMessageAsyncNew()
{
var client = new MyServiceClient();
var result = await client.SendMessageAsync("Message");
return result;
}

Этот "облегченный" вариант использования await стал доступен в .Net Framework 4.5.

Асинхронный подход на основе задач решил большинство проблем предыдущих шаблонов. Тут вам и возможность прервать асинхронную операцию, и простая реализация одним методом, возможность отслеживать прогресс выполнения операции.

На текущий момент Microsoft рекомендует использовать именно этот шаблон для реализации асинхронных вызовов при разработке компонентов.

Что же касается целесообразности использования этого шаблона. Использование TAP увеличит пропускную способность сервера. Однако, расходы на создание асинхронного процесса могут нивелировать преимущества, если у вас интенсивность обмена (например, клиент-сервера) небольшая. В этом случае синхронный подход будет работать быстрее.

Я понимаю, что мы лишь "пробежались по верхушкам", и этой информации недостаточно для хорошего понимания принципа работы разных подходов к асинхронному программированию. И поэтому, мы рассмотрим детально каждый шаблон отдельно, в последующих статьях.

9 ноября 2023

Как сделать отчет из C# проекта в МоиОтчеты Облако

В этой статье разберем пример, как с помощью SDK FastReport создавать отчеты и экспортировать их в любой удобный для вас формат.
22 марта 2023

Создание PDF отчета в JetBrains Rider (C#) на «Альт Рабочая станция К» 10

В этой статье мы взглянем на платформу .NET в «Альт Рабочая станция К» 10 и создадим отчет, который можно экспортировать в PDF.
19 декабря 2022

Создание PDF отчета в JetBrains Rider (C#) на Windows 11

В этой статье мы взглянем на .NET в Windows 11 без использования Microsoft Visual Studio, и экспортируем отчет в формат PDF.