Заметка
Доступ к этой странице требует авторизации. Вы можете попробовать войти в систему или изменить каталог.
Доступ к этой странице требует авторизации. Вы можете попробовать сменить директорию.
Вы можете избежать узких мест производительности и повысить общую скорость реагирования приложения с помощью асинхронного программирования. Однако традиционные методы написания асинхронных приложений могут быть сложными, что затрудняет их запись, отладку и обслуживание.
C# поддерживает упрощенный подход, асинхронное программирование, которое использует асинхронную поддержку в среде выполнения .NET. Компилятор выполняет сложную работу, которую разработчик использовал для выполнения, и приложение сохраняет логическую структуру, которая напоминает синхронный код. В результате вы получаете все преимущества асинхронного программирования с частью усилий.
Эта статья содержит общие сведения о том, когда и как использовать асинхронное программирование и содержит ссылки на другие статьи, содержащие подробные сведения и примеры.
Асинхрон улучшает скорость реагирования
Асинхронность важна для действий, которые могут блокировать, например, доступ к веб-ресурсам. Доступ к веб-ресурсу иногда замедляется или задерживается. Если такое действие блокируется в синхронном процессе, все приложение должно ждать. В асинхронном процессе приложение может продолжать работу, которая не зависит от веб-ресурса, пока потенциально блокирующая задача не завершится.
В следующей таблице показаны типичные области, в которых асинхронное программирование повышает скорость реагирования. Перечисленные API из .NET и среды выполнения Windows содержат методы, поддерживающие асинхронное программирование.
| Область приложения | Типы .NET с асинхронными методами | Типы среды выполнения Windows с асинхронными методами |
|---|---|---|
| Веб-доступ | HttpClient | Windows.Web.Http.HttpClient SyndicationClient |
| Работа с файлами | JsonSerializer StreamReader StreamWriter XmlReader XmlWriter |
StorageFile |
| Работа с изображениями | MediaCapture BitmapEncoder BitmapDecoder |
|
| Программирование WCF | Синхронные и асинхронные операции |
Асинхронность является особенно ценной для приложений, которые обращаются к потоку пользовательского интерфейса, так как все UI-активности обычно используют один поток. Если любой процесс заблокирован в синхронном приложении, все блокируются. Приложение перестает отвечать, и вы можете предположить, что оно не работает, хотя на самом деле оно просто ждет.
При использовании асинхронных методов приложение продолжает реагировать на пользовательский интерфейс. Можно изменить размер или свернуть окно, например, или закрыть приложение, если вы не хотите ждать завершения.
Асинхронный подход добавляет эквивалент автоматической передачи в список параметров, которые можно выбрать при разработке асинхронных операций. То есть вы получаете все преимущества традиционного асинхронного программирования, но с гораздо меньшими усилиями от разработчика.
Асинхронные методы легко записывать
Ключевые слова async и await в C# являются сердцем асинхронного программирования. С помощью этих двух ключевых слов можно использовать ресурсы в .NET Framework, .NET Core или среде выполнения Windows для создания асинхронного метода почти так же легко, как и при создании синхронного метода. Асинхронные методы, определяемые с помощью ключевого async слова, называются асинхронными методами.
В следующем примере показан асинхронный метод. Почти все в коде должно выглядеть знакомо.
Вы можете найти полный пример Windows Presentation Foundation (WPF), доступный для скачивания из Асинхронное программирование с использованием async и await в C#.
public async Task<int> GetUrlContentLengthAsync()
{
using var client = new HttpClient();
Task<string> getStringTask =
client.GetStringAsync("https://learn.microsoft.com/dotnet");
DoIndependentWork();
string contents = await getStringTask;
return contents.Length;
}
void DoIndependentWork()
{
Console.WriteLine("Working...");
}
Вы можете научиться нескольким практикам из предыдущего примера. Начните с сигнатуры метода. Он включает модификатор async . Тип возвращаемого значения — Task<int> (см. раздел "Возвращаемые типы" для получения дополнительных параметров). Имя метода заканчивается Async. В тексте метода GetStringAsync возвращается Task<string>. Это означает, что когда вы await, вы получаете string (contents). Перед ожиданием задачи можно выполнить работу, которая не зависит от stringGetStringAsync.
Обратите внимание на await оператора. Приостанавливает GetUrlContentLengthAsync:
-
GetUrlContentLengthAsyncне может продолжаться, покаgetStringTaskне завершится. - Между тем контроль возвращается вызывающей стороне
GetUrlContentLengthAsync. - Управление возобновляется здесь после завершения
getStringTask. - Затем
awaitоператор извлекаетstringрезультат изgetStringTask.
Оператор return устанавливает целочисленный результат. Все методы, ожидающие GetUrlContentLengthAsync получения значения длины.
Если GetUrlContentLengthAsync у него нет никаких действий, которые он может сделать между вызовом GetStringAsync и ожиданием его завершения, можно упростить код, вызвав и ожидая в следующей одной инструкции.
string contents = await client.GetStringAsync("https://learn.microsoft.com/dotnet");
Следующие характеристики обобщают то, что делает предыдущий пример асинхронным методом:
Сигнатура
asyncметода включает модификатор.Имя асинхронного метода по соглашению заканчивается суффиксом Async.
Возвращаемый тип является одним из следующих типов:
-
Task<TResult> Если метод имеет оператор return, в котором операнд имеет тип
TResult. - Task Если метод не имеет инструкции return или имеет оператор return без операнда.
-
voidЕсли вы пишете асинхронный обработчик событий. - Любой другой тип, имеющий метод
GetAwaiter.
Дополнительные сведения см. в разделе "Типы возврата" и "Параметры ".
-
Task<TResult> Если метод имеет оператор return, в котором операнд имеет тип
Обычно этот метод включает по крайней мере одно
awaitвыражение, которое помечает точку, в которой метод не может продолжаться до завершения ожидаемой асинхронной операции. В то же время метод приостановлен, а управление возвращается вызвавшему его. В следующем разделе этой статьи показано, что происходит в точке приостановки.
В асинхронных методах используйте указанные ключевые слова и типы, чтобы указать, что нужно сделать, и компилятор выполняет остальные действия, включая отслеживание того, что должно произойти при возврате элемента управления в точку ожидания в приостановленном методе. Некоторые обычные процессы, такие как циклы и обработка исключений, могут быть трудно обрабатывать в традиционном асинхронном коде. В асинхронном методе эти элементы записываются так же, как и в синхронном решении, и проблема решена.
Дополнительные сведения об асинхронности в предыдущих версиях .NET Framework см. в статье TPL и традиционное асинхронное программирование .NET Framework.
Что происходит в асинхронном методе
Самое главное, чтобы понять в асинхронном программировании, заключается в том, как поток управления перемещается из метода в метод. Диаграмма ниже проведет вас через процесс:
Числа на схеме соответствуют следующим шагам, которые начинаются, когда вызывающий метод вызывает асинхронный метод.
Вызывающий метод вызывает и ожидает асинхронный
GetUrlContentLengthAsyncметод.GetUrlContentLengthAsyncсоздаёт экземпляр HttpClient и вызывает асинхронный метод GetStringAsync для загрузки содержимого веб-сайта в виде строки.Что-то происходит в
GetStringAsync, что приостанавливает его продвижение. Возможно, он должен ждать загрузки веб-сайта или другого блокирующего действия. Чтобы избежать блокировки ресурсов,GetStringAsyncвозвращает управление вызывающим объектуGetUrlContentLengthAsync.GetStringAsyncTask<TResult>возвращает значение , гдеTResultявляется строка иGetUrlContentLengthAsyncназначает задачу переменнойgetStringTask. Задача представляет текущий процесс вызоваGetStringAsyncс обязательством создать фактическое строковое значение, когда работа будет завершена.Поскольку
getStringTaskеще не завершен,GetUrlContentLengthAsyncможет выполнять другие задачи, которые не зависят от конечного результата изGetStringAsync. Эта работа представлена вызовом синхронного методаDoIndependentWork.DoIndependentWork— синхронный метод, который выполняет свою работу и возвращает управление вызывающему.GetUrlContentLengthAsyncзаканчивает всю работу, которую может выполнять без результата отgetStringTask.GetUrlContentLengthAsyncдалее требуется вычислить и вернуть длину скачаемой строки, но метод не может вычислить это значение, пока метод не будет содержать строку.Поэтому
GetUrlContentLengthAsyncиспользует оператор await для приостановки выполнения и передачи управления методу, который вызвалGetUrlContentLengthAsync.GetUrlContentLengthAsyncвозвращаетTask<int>вызывающему. Задача представляет обещание создать целочисленный результат, который является длиной скачаемой строки.Примечание.
Если
GetStringAsync(и поэтомуgetStringTask) завершается до того, какGetUrlContentLengthAsyncего ожидает, управление остается вGetUrlContentLengthAsync. Расходы на приостановку и последующее возвращение кGetUrlContentLengthAsyncокажутся напрасными, если вызываемый асинхронный процессgetStringTaskзавершен иGetUrlContentLengthAsyncне должен ждать окончательного результата.Обработка продолжается внутри вызывающего метода. Вызывающий может выполнить другие действия, не зависящие от результата
GetUrlContentLengthAsync, прежде чем ожидать этот результат, или может ожидать немедленно. Вызывающий метод ожидаетGetUrlContentLengthAsync, аGetUrlContentLengthAsyncожидаетGetStringAsync.GetStringAsyncзавершает и создает строковый результат. Строковый результат не возвращается вызовомGetStringAsyncтак, как вы могли бы ожидать. (Помните, что метод уже вернул задачу на шаге 3.) Вместо этого строковый результат хранится в задаче, представляющей завершение методаgetStringTask. Оператор await извлекает результат изgetStringTask. Инструкция назначения назначает полученный результатcontents.При
GetUrlContentLengthAsyncполучении результата строки метод может вычислить длину строки. Затем работаGetUrlContentLengthAsyncтакже завершена, а обработчик события ожидания может возобновить свою работу. В полном примере, представленном в конце статьи, можно убедиться, что обработчик событий извлекает и распечатывает значение результата длины. Если вы не знакомы с асинхронным программированием, обратите внимание на разницу между синхронным и асинхронным поведением. Синхронный метод возвращается после завершения работы (шаг 5), но асинхронный метод возвращает значение задачи при приостановке работы (шаги 3 и 6). Когда асинхронный метод в конечном итоге завершит свою работу, задача помечается как завершенная, а результат, если он есть, хранится в задаче.
Асинхронные методы API
Вы, возможно, задумываетесь, где можно найти такие методы, как GetStringAsync, которые поддерживают асинхронное программирование. .NET Framework 4.5 или более поздней версии и .NET Core содержат множество элементов, которые работают с async и await. Их можно распознать по суффиксу Async, добавленному к имени члена, и по их типу возвращаемого значения, как Task или Task<TResult>. Например, System.IO.Stream класс содержит такие методы, как CopyToAsync, ReadAsyncи WriteAsync наряду с синхронными методами CopyTo, Readи Write.
Среда выполнения Windows также содержит множество методов, которые можно использовать с async и await в приложениях Windows. Дополнительные сведения см. в статьях "Потоки и асинхронное программирование" для разработки UWP, "Асинхронное программирование (приложения Магазина Windows)" и "Краткое руководство: Вызов асинхронных API в C# или Visual Basic" при использовании более ранних версий среды выполнения Windows.
Потоки
Асинхронные методы предназначены для неблокирующих операций. Выражение await в асинхронном методе не блокирует текущий поток во время выполнения ожидаемой задачи. Вместо этого выражение регистрирует остальную часть метода как продолжение и возвращает управление вызывающему асинхронный метод.
Ключевые слова async и await не приводят к созданию дополнительных потоков. Для асинхронных методов не требуется многопоточность, так как асинхронный метод не выполняется в собственном потоке. Метод выполняется в текущем контексте синхронизации и использует время в потоке, только если метод активен. Вы можете использовать Task.Run для перемещения работы, привязанной к ЦП, в фоновый поток, но фоновый поток не помогает в процессе, который просто ожидает получения результатов.
Асинхронный подход к асинхронным программированию предпочтительнее существующих подходов практически в каждом случае. В частности, этот подход лучше, чем BackgroundWorker класс для операций ввода-вывода, так как код проще, и вам не нужно защищаться от условий гонки. В сочетании с методом Task.Run, асинхронное программирование лучше, чем BackgroundWorker, для операций, связанных с загрузкой ЦП, поскольку оно отделяет координацию выполнения кода от задач, которые Task.Run передает в пул потоков.
Асинхронная и ожидание
Если указать, что метод является асинхронным методом с помощью асинхронного модификатора, включите следующие две возможности.
Помеченный асинхронный метод может использовать ожидание для назначения точек приостановки. Оператор
awaitсообщает компилятору, что асинхронный метод не может продолжаться до завершения ожидаемого асинхронного процесса. В это время управление возвращается вызывающему асинхронный метод.Приостановка асинхронного метода в
awaitвыражении не представляет собой выход из метода, иfinallyблоки не выполняются.Помеченный асинхронный метод может ожидаться методами, вызывающими его.
Асинхронный метод обычно содержит одно или несколько вхождений await оператора, но отсутствие await выражений не приводит к ошибке компилятора. Если асинхронный метод не использует await оператор для маркировки точки приостановки, метод выполняется как синхронный метод, несмотря на async модификатор. Компилятор выдает предупреждение для таких методов.
async и await являются контекстными ключевыми словами. Дополнительные сведения и примеры см. в следующих статьях:
Возвращаемые типы и параметры
Асинхронный метод обычно возвращает Task или Task<TResult>. В асинхронном методе оператор await применяется к задаче, возвращаемой из вызова другого асинхронного метода.
Тип Task<TResult> указывается как возвращаемое значение, если метод содержит оператор return, указывающий операнд типа TResult.
Вы используете Task в качестве типа возвращаемого значения, если метод не имеет инструкции return или имеет оператор return, который не возвращает операнду.
Можно также указать любой другой возвращаемый тип, если тип содержит метод GetAwaiter.
ValueTask<TResult> пример такого типа. Он доступен в пакете NuGet System.Threading.Tasks.Extension .
В следующем примере показано, как объявлять и вызывать метод, который возвращает Task<TResult> или Task.
async Task<int> GetTaskOfTResultAsync()
{
int hours = 0;
await Task.Delay(0);
return hours;
}
Task<int> returnedTaskTResult = GetTaskOfTResultAsync();
int intResult = await returnedTaskTResult;
// Single line
// int intResult = await GetTaskOfTResultAsync();
async Task GetTaskAsync()
{
await Task.Delay(0);
// No return statement needed
}
Task returnedTask = GetTaskAsync();
await returnedTask;
// Single line
await GetTaskAsync();
Каждая возвращаемая задача представляет текущую работу. Задача инкапсулирует сведения о состоянии асинхронного процесса и в конечном счёте либо конечный результат этого процесса, либо исключение, которое вызывает процесс, если он терпит неудачу.
Асинхронный метод также может иметь тип возвращаемого void значения. Этот тип возвращаемого значения используется в основном для определения обработчиков событий, где требуется возвращаемый тип void. Асинхронные обработчики событий часто служат отправной точкой для асинхронных программ.
Асинхронный метод с возвращаемым типом void не может быть ожидаем, и вызывающий метод с возвращаемым типом void не может перехватывать исключения, которые он может выбрасывать.
Асинхронный метод не может объявлять параметры с модификаторами in, ref или out, но может вызывать методы, в которых такие параметры используются. Аналогичным образом асинхронный метод не может возвращать значение по ссылке, хотя он может вызывать методы с возвращаемыми значениями ссылок.
Дополнительные сведения и примеры см. в статье async return types (C#).
Асинхронные API в программировании среды выполнения Windows имеют один из следующих типов возвращаемых значений, аналогичных задачам:
- IAsyncOperation<TResult>, соответствующий Task<TResult>
- IAsyncAction, соответствующий Task
- IAsyncActionWithProgress<TProgress>
- IAsyncOperationWithProgress<TResult,TProgress>
Соглашение об именовании
По соглашению методы, возвращающие часто ожидаемые типы (например, Task, Task<T>, , ValueTask, ValueTask<T>) должны иметь имена, заканчивающиеся "Async". Методы, которые запускают асинхронную операцию, но не возвращают ожидаемый тип, не должны иметь имена, заканчивающиеся "Async", но могут начинаться с "Begin", "Start" или другой командой, чтобы предложить этот метод не возвращать или вызывать результат операции.
Вы можете игнорировать соглашение, в котором событие, базовый класс или контракт интерфейса предлагает другое имя. Например, не следует переименовать распространенные обработчики событий, например OnButtonClick.
Связанные статьи (Visual Studio)
| Заголовок | Описание |
|---|---|
| Как выполнять несколько веб-запросов параллельно с использованием async и await (C#) | Демонстрирует, как одновременно запускать несколько задач. |
| Асинхронные возвращаемые типы (C#) | Иллюстрирует типы, которые асинхронные методы могут возвращать, и объясняет, когда каждый тип подходит. |
| Отменяйте задачи, используя токен отмены в качестве механизма сигнализации. | Показывает, как добавить следующие функции в асинхронное решение: - Отмена списка задач (C#) - Отмена задач после периода времени (C#) - Обработка асинхронных задач по мере их завершения (C#) |
| Использование асинхронного доступа к файлам (C#) | Перечисляет и демонстрирует преимущества использования async и await для доступа к файлам. |
| Асинхронный шаблон на основе задач (TAP) | Описывает асинхронный шаблон. Шаблон основан на типах Task и Task<TResult>. |
| Асинхронные видео на канале 9 | Содержит ссылки на различные видеоролики об асинхронном программировании. |