Примечание
Для доступа к этой странице требуется авторизация. Вы можете попробовать войти или изменить каталоги.
Для доступа к этой странице требуется авторизация. Вы можете попробовать изменить каталоги.
В .NET асинхронный шаблон на основе задач — это рекомендуемый шаблон асинхронного проектирования для новой разработки. Типах Task и Task<TResult> в пространстве имен System.Threading.Tasks, которые используются для представления асинхронных операций.
Именование, параметры и возвращаемые типы
TAP использует один метод для представления инициализации и завершения асинхронной операции. Это отличается от шаблона асинхронного программирования (APM или IAsyncResult
) и асинхронного шаблона на основе событий (EAP). Для APM требуются Begin
и End
методы. Для EAP нужен метод с суффиксом Async
, а также требуется одно или несколько событий, типы делегатов обработчика событий и производные от EventArg
типы. Асинхронные методы в TAP включают Async
суффикс после имени операции для методов, возвращающих ожидаемые типы, такие как Task, Task<TResult>и ValueTaskValueTask<TResult>. Например, асинхронная операция Get
, возвращающая Task<String>
, может быть названа GetAsync
. Если вы добавляете метод TAP в класс, который уже содержит имя метода EAP с суффиксом Async
, используйте суффикс TaskAsync
. Например, если класс уже имеет GetAsync
метод, используйте имя GetTaskAsync
. Если метод запускает асинхронную операцию, но не возвращает ожидаемый тип данных, его имя должно начинаться с Begin
, Start
или другого глагола, чтобы указывать, что этот метод не возвращает и не бросает результат операции.
Метод TAP возвращает либо System.Threading.Tasks.Task, либо System.Threading.Tasks.Task<TResult>, в зависимости от того, возвращает ли соответствующий синхронный метод void или тип TResult
.
Параметры метода TAP должны соответствовать параметрам его синхронного аналога и должны быть предоставлены в том же порядке.
out
Однако и ref
параметры исключаются из этого правила и должны быть полностью исключены. Данные, которые были бы возвращены через параметр out
или ref
, необходимо возвращать как часть TResult
, возвращаемую с помощью Task<TResult>, и для этого следует использовать кортеж или пользовательскую структуру данных для одновременного представления нескольких значений. Кроме того, рекомендуется добавить CancellationToken параметр, даже если синхронный аналог метода TAP не предлагает его.
Методы, посвященные исключительно созданию, манипуляции или сочетанию задач (где асинхронное намерение метода ясно в имени метода или в имени типа, к которому принадлежит метод), не нужно следовать этому шаблону именования; такие методы часто называются комбинаторами. Примеры комбинаторов, такие как WhenAll и WhenAny, рассматриваются в разделе «Использование встроенных комбинаторов на основе задач» статьи «Использование асинхронного шаблона на основе задач».
Примеры того, как синтаксис TAP отличается от синтаксиса, используемого в устаревших асинхронных шаблонах программирования, таких как модель асинхронного программирования (APM) и асинхронный шаблон на основе событий (EAP), см. в шаблонах асинхронного программирования.
Инициирование асинхронной операции
Асинхронный метод, основанный на TAP, может выполнять небольшую работу синхронно, например проверяя аргументы и инициируя асинхронную операцию, прежде чем она возвращает итоговую задачу. Синхронная работа должна храниться до минимума, чтобы асинхронный метод быстро возвращался. Ниже перечислены причины быстрого возврата:
Асинхронные методы могут вызываться из потоков пользовательского интерфейса, и любая долго выполняющаяся синхронная работа может повредить отклику приложения.
Несколько асинхронных методов могут запускаться одновременно. Поэтому любая длительная работа в синхронной части асинхронного метода может отложить запуск других асинхронных операций, тем самым уменьшая преимущества параллелизма.
В некоторых случаях объем работы, необходимой для выполнения операции, меньше объема работы, необходимой для асинхронного запуска операции. Чтение из потока, в котором операция чтения может быть удовлетворена данными, которые уже буферизуются в памяти, является примером такого сценария. В таких случаях операция может выполняться синхронно и может возвращать задачу, которая уже завершена.
Исключения
Асинхронный метод должен вызывать исключение из асинхронного вызова метода только в ответ на ошибку использования. Ошибки использования никогда не должны возникать в рабочем коде. Например, если передача ссылки null (Nothing
в Visual Basic) в качестве одного из аргументов метода вызывает состояние ошибки, обычно представляемое исключением ArgumentNullException, можно изменить вызывающий код, чтобы убедиться в том, что ссылка null никогда не передается. Для всех других ошибок исключения, возникающие при выполнении асинхронного метода, должны быть назначены возвращаемой задаче, даже если асинхронный метод выполняется синхронно перед возвратом задачи. Как правило, задача содержит не более одного исключения. Однако если задача представляет несколько операций (например, WhenAll), с одной задачей может быть связано несколько исключений.
Целевая среда
При реализации метода TAP можно определить, где происходит асинхронное выполнение. Вы можете выполнить рабочую нагрузку в пуле потоков, реализовать ее с помощью асинхронного ввода-вывода (без привязки к потоку для большинства выполнения операции), запустить его в определенном потоке (например, потоке пользовательского интерфейса) или использовать любое количество потенциальных контекстов. Метод TAP может не иметь ничего для выполнения и может просто вернуть Task, представляющее возникновение условия в другой части системы (например, задача, представляющая данные, поступающие в структуру данных с очередью).
Вызывающий метод TAP может блокировать ожидание завершения метода TAP путем синхронного ожидания результирующей задачи или выполнения дополнительного (продолжения) кода при завершении асинхронной операции. Создатель кода продолжения имеет контроль над тем, где выполняется этот код. Код продолжения можно создать явным образом, используя методы класса Task (например, ContinueWith) или неявно, используя языковую поддержку, созданную на основе продолжения (например await
, в C#, Await
AwaitValue
в Visual Basic в F#).
Состояние задачи
Класс Task предоставляет жизненный цикл для асинхронных операций, и этот цикл представлен TaskStatus перечислением. Для поддержки особых случаев типов, производных от Task, а также Task<TResult>, и для разделения процесса построения от процесса планирования, класс Task содержит метод Start. Задачи, созданные общедоступными Task конструкторами, называются холодными задачами, так как они начинают жизненный цикл в непланированном Created состоянии и планируются только при Start вызове этих экземпляров.
Все остальные задачи начинают жизненный цикл в горячем состоянии, что означает, что асинхронные операции, которые они представляют, уже были инициированы, и их состояние задачи — это значение перечисления, отличное от TaskStatus.Createdзначения. Все задачи, возвращаемые из методов TAP, должны быть активированы. Если метод TAP использует конструктор задачи для создания задачи для возврата, метод TAP должен вызывать Start на Task объекте, прежде чем вернуть его. Потребители метода TAP могут безопасно предположить, что возвращаемая задача активна, и они не должны пытаться вызвать Start на каком-либо Task, возвращенном из метода TAP. Вызов Start у активной задачи вызывает исключение InvalidOperationException.
Отмена (необязательно)
В TAP отмена является необязательной как для разработчиков асинхронных методов, так и для потребителей асинхронных методов. Если операция позволяет отмену, она предлагает перегрузку асинхронного метода, который принимает токен отмены (экземпляр CancellationToken). По соглашению параметр называется cancellationToken
.
public Task ReadAsync(byte [] buffer, int offset, int count,
CancellationToken cancellationToken)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
count As Integer,
cancellationToken As CancellationToken) _
As Task
Асинхронная операция отслеживает этот маркер для запросов на отмену. Если он получает запрос на отмену, он может принять этот запрос и отменить операцию. Если запрос на отмену приводит к преждевременному завершению работы, метод TAP возвращает задачу, которая заканчивается в Canceled состоянии; результат не доступен и исключения не выбрасываются. Состояние Canceled считается окончательным (завершенным) для задачи вместе с состоянием Faulted и RanToCompletion состояниями. Таким образом, если задача находится в Canceled состоянии, её IsCompleted свойство возвращает true
. Когда задача завершается в состоянии Canceled, все продолжения, зарегистрированные с задачей, запланированы или выполнены, если, конечно, не был указан параметр продолжения, такой как NotOnCanceled, чтобы отказаться от продолжения. Любой код, который асинхронно ожидает выполнения отменённой задачи, используя языковые особенности, продолжает выполняться, но получает OperationCanceledException или исключение, производное от него. Код, который блокируется синхронно в ожидании задачи, например, с помощью методов Wait и WaitAll, также продолжает работу при возникновении исключения.
Если маркер отмены запросил отмену до вызова метода TAP, который принимает этот маркер, метод TAP должен вернуть Canceled задачу. Однако если отмена запрашивается во время выполнения асинхронной операции, асинхронная операция не должна принимать запрос на отмену. Возвращаемая задача должна находиться в состоянии Canceled только в том случае, если операция завершается в результате запроса на отмену. Если отмена запрашивается, но результат или исключение по-прежнему появляется, задача должна завершиться в RanToCompletion или Faulted.
Для асинхронных методов, которые хотят предоставить возможность отмены в первую очередь, вам не нужно предоставлять перегрузку, которая не принимает маркер отмены. Для методов, которые не могут быть отменены, не предоставляйте перегрузки, принимающие маркер отмены; это помогает указать вызывающему, может ли целевой метод быть отменён. Потребительский код, который не требует отмены, может вызвать метод, который принимает CancellationToken и предоставляет None в качестве значения аргумента. None функционально эквивалентен значению по умолчанию CancellationToken.
Отчеты о ходе выполнения (необязательно)
Некоторые асинхронные операции получают преимущество от предоставления уведомлений о ходе выполнения; Обычно они используются для обновления пользовательского интерфейса с информацией о ходе асинхронной операции.
В TAP прогресс обрабатывается через IProgress<T> интерфейс, передаваемый в асинхронный метод как параметр, обычно называемый progress
. Предоставление интерфейса прогресса при вызове асинхронного метода помогает устранить состояния гонки, возникающие из-за неправильного использования, то есть из-за неправильной регистрации обработчиков событий после запуска операции, что может привести к пропуску обновлений. Что более важно, интерфейс прогресса поддерживает различные реализации прогресса, как определено кодом потребителя. Например, потребляющий код может заботиться только о последнем обновлении хода выполнения или может потребоваться буферизировать все обновления, или может потребоваться вызвать действие для каждого обновления, или может потребоваться контролировать, маршалируется ли вызов в определенный поток. Все эти параметры могут быть достигнуты с помощью другой реализации интерфейса, настраиваемой в соответствии с потребностями конкретного потребителя. Как и при отмене, реализации TAP должны предоставлять IProgress<T> параметр только в том случае, если API поддерживает уведомления о ходе выполнения.
Например, если метод ReadAsync
, рассмотренный ранее в этой статье, может сообщить о промежуточном прогрессе в виде количества байтов, прочитанных до сих пор, обратный вызов хода выполнения может быть интерфейсом IProgress<T> :
public Task ReadAsync(byte[] buffer, int offset, int count,
IProgress<long> progress)
Public Function ReadAsync(buffer() As Byte, offset As Integer,
count As Integer,
progress As IProgress(Of Long)) As Task
FindFilesAsync
Если метод возвращает список всех файлов, которые соответствуют определенному шаблону поиска, обратный вызов хода выполнения может дать оценку процента завершенной работы и текущего набора частичных результатов. Можно предоставить эту информацию в виде кортежа:
public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<Tuple<double,
ReadOnlyCollection<List<FileInfo>>>> progress)
Public Function FindFilesAsync(pattern As String,
progress As IProgress(Of Tuple(Of Double, ReadOnlyCollection(Of List(Of FileInfo))))) _
As Task(Of ReadOnlyCollection(Of FileInfo))
или с типом данных, характерным для API:
public Task<ReadOnlyCollection<FileInfo>> FindFilesAsync(
string pattern,
IProgress<FindFilesProgressInfo> progress)
Public Function FindFilesAsync(pattern As String,
progress As IProgress(Of FindFilesProgressInfo)) _
As Task(Of ReadOnlyCollection(Of FileInfo))
В последнем случае к специальному типу данных обычно добавляется суффикс ProgressInfo
.
Если реализации TAP предоставляют перегрузки, принимаюющие progress
параметр, они должны разрешить аргументу быть null
, в этом случае не сообщается о ходе выполнения. Реализации TAP должны сообщать о ходе выполнения Progress<T> объекта синхронно, что позволяет асинхронному методу быстро обеспечить ход выполнения. Он также позволяет пользователю данных о прогрессе определить, каким образом и где целесообразнее всего обрабатывать информацию. Например, экземпляр хода выполнения может выбрать маршалирование обратных вызовов и вызвать события в контексте записанной синхронизации.
Реализации IProgress<T>
.NET предоставляет Progress<T> класс, реализующий IProgress<T>. Класс Progress<T> объявлен следующим образом:
public class Progress<T> : IProgress<T>
{
public Progress();
public Progress(Action<T> handler);
protected virtual void OnReport(T value);
public event EventHandler<T>? ProgressChanged;
}
Экземпляр Progress<T> предоставляет ProgressChanged событие, которое вызывается каждый раз, когда асинхронная операция сообщает о ходе выполнения. Событие ProgressChanged возникает на объекте SynchronizationContext, который был захвачен при создании экземпляра Progress<T>. Если контекст синхронизации недоступен, используется контекст по умолчанию, предназначенный для пула потоков. Обработчики могут быть зарегистрированы в этом событии. Один обработчик также может быть предоставлен Progress<T> конструктору для удобства и работает точно так же, как обработчик событий для ProgressChanged события. Обновления хода выполнения отправляются асинхронно, чтобы избежать задержки асинхронной операции во время обработки обработчиков событий. Другая IProgress<T> реализация может выбрать применение другой семантики.
Выбор перегрузки для предоставления
Если реализация TAP использует оба необязательных параметра CancellationToken и IProgress<T>, это может потребовать до четырех перегрузок.
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);
public Task MethodNameAsync(…, IProgress<T> progress);
public Task MethodNameAsync(…,
CancellationToken cancellationToken, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken cancellationToken) As Task
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
progress As IProgress(Of T)) As Task
Однако многие реализации TAP не предоставляют возможности отмены или отслеживания хода выполнения, поэтому требуется воспользоваться одним методом.
public Task MethodNameAsync(…);
Public MethodNameAsync(…) As Task
Если реализация TAP поддерживает отмену или ход выполнения, но не оба, это может привести к двум перегрузкам:
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, CancellationToken cancellationToken);
// … or …
public Task MethodNameAsync(…);
public Task MethodNameAsync(…, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken) As Task
' … or …
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, progress As IProgress(Of T)) As Task
Если реализация TAP поддерживает отмену и отслеживание выполнения, она может предоставить все четыре перегрузки. Однако он может предоставить только следующие два:
public Task MethodNameAsync(…);
public Task MethodNameAsync(…,
CancellationToken cancellationToken, IProgress<T> progress);
Public MethodNameAsync(…) As Task
Public MethodNameAsync(…, cancellationToken As CancellationToken,
progress As IProgress(Of T)) As Task
Чтобы компенсировать два отсутствующих промежуточных сочетания, разработчики могут передать None или по умолчанию CancellationToken для cancellationToken
параметра и null
для progress
параметра.
Если вы ожидаете, что каждое использование метода TAP поддерживает отмену или ход выполнения, вы можете опустить перегрузки, которые не принимают соответствующий параметр.
Если вы решите предоставить несколько перегрузок, чтобы сделать отмену или ход выполнения необязательным, перегрузки, которые не поддерживают отмену или ход выполнения, должны вести себя так, как если бы они прошли None для отмены или null
для хода выполнения перегрузки, которая поддерживает эти функции.
Связанные статьи
Название | Описание |
---|---|
Асинхронные шаблоны программирования | Содержит три шаблона для выполнения асинхронных операций: асинхронный шаблон на основе задач (TAP), асинхронную модель программирования (APM) и асинхронную модель на основе событий (EAP). |
Реализация асинхронного шаблона на основе задач | Описывает, как реализовать асинхронный шаблон на основе задач (TAP) тремя способами: с помощью компиляторов C# и Visual Basic в Visual Studio, вручную или с помощью сочетания методов компилятора и вручную. |
Использование асинхронного шаблона на основе задач | Описывает, как можно использовать задачи и обратные вызовы для организации ожидания без блокировки. |
Взаимодействие с другими асинхронными шаблонами и типами | Описывает использование асинхронного шаблона на основе задач (TAP) для реализации асинхронной модели программирования (APM) и асинхронного шаблона на основе событий (EAP). |