Ескертпе
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Жүйеге кіруді немесе каталогтарды өзгертуді байқап көруге болады.
Бұл бетке кіру үшін қатынас шегін айқындау қажет. Каталогтарды өзгертуді байқап көруге болады.
В .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), см. в шаблонах асинхронного программирования.
Асинхронное поведение, возвращаемые типы и именование
Ключевое async слово не заставляет метод выполняться асинхронно в другом потоке. Он активирует await, и метод выполняется синхронно, пока не достигнет неполного ожидаемого объекта. Если метод не достигает ожидаемого объекта, он может синхронно завершить выполнение.
Для большинства API предпочитают следующие типы возвращаемых значений:
- Используйте Task для асинхронных операций, которые не возвращают значения.
- Используйте Task<TResult> для асинхронных операций, которые возвращают значение.
- Используйте ValueTask или ValueTask<TResult> только если измерения показывают нагрузку на ресурсы и когда потребители могут выдерживать дополнительные ограничения использования.
Сохраняйте именование TAP предсказуемым:
-
AsyncИспользуйте суффикс для методов, возвращающих ожидаемые типы. - Не добавляйте
Asyncк синхронным методам. - Добавьте новую
MethodNameAsyncперегрузку наряду с существующим "MethodName". Не удаляйте или не переименуйте синхронный API. Сохранение обоих позволяет пользователям выполнять миграцию в собственном темпе без разрушительных изменений.
Инициирование асинхронной операции
Асинхронный метод, основанный на TAP, может выполнять небольшую работу синхронно, например проверяя аргументы и инициируя асинхронную операцию, прежде чем она возвращает итоговую задачу. Сохраняйте синхронную работу до минимума, чтобы асинхронный метод смог быстро вернуться. Ниже перечислены причины быстрого возврата:
- Вы можете вызвать асинхронные методы из потоков пользовательского интерфейса, и любая длительная синхронная работа может повредить отклику приложения.
- Вы можете одновременно запускать несколько асинхронных методов. Поэтому любая длительная работа в синхронной части асинхронного метода может отложить запуск других асинхронных операций, тем самым уменьшая преимущества параллелизма.
В некоторых случаях объем работы, необходимой для выполнения операции, меньше объема работы, необходимой для асинхронного запуска операции. Чтение из потока, в котором операция чтения может быть удовлетворена данными, которые уже буферизуются в памяти, является примером такого сценария. В таких случаях операция может выполняться синхронно и может возвращать задачу, которая уже завершена.
Исключения
Асинхронный метод должен вызывать исключение непосредственно из вызова асинхронного метода только в ответ на ошибку использования. Ошибки использования никогда не должны возникать в рабочем коде. Например, если передача ссылки null (Nothing в Visual Basic) в качестве одного из аргументов метода вызывает состояние ошибки, обычно представляемое исключением ArgumentNullException, можно изменить вызывающий код, чтобы убедиться в том, что ссылка null никогда не передается. Для всех других ошибок назначьте исключения, возникающие при выполнении асинхронного метода возвращаемой задаче, даже если асинхронный метод выполняется синхронно до возврата задачи. Как правило, задача содержит не более одного исключения. Однако если задача представляет несколько операций (например, WhenAll), может быть связано несколько исключений с одной задачей.
Целевая среда
При реализации метода TAP можно определить, где происходит асинхронное выполнение. Вы можете выполнить рабочую нагрузку в пуле потоков, реализовать ее с помощью асинхронного ввода-вывода (без привязки к потоку для большинства выполнения операции), запустить его в определенном потоке (например, поток пользовательского интерфейса) или использовать любое количество потенциальных контекстов. Метод TAP может даже не иметь ничего для выполнения и может просто вернуть Task, представляющий возникновение условия в другой части системы (например, задача, которая представляет собой данные, поступающие в структуру данных в очереди).
Вызывающий метод TAP может блокировать ожидание завершения метода TAP путем синхронного ожидания результирующей задачи или выполнения дополнительного (продолжения) кода при завершении асинхронной операции. Создатель кода продолжения имеет контроль над тем, где выполняется этот код. Код продолжения можно создать явным образом, используя методы класса Task (например, ContinueWith) или неявно, используя языковую поддержку, созданную на основе продолжения (например, await в C#, Await в Visual Basic, AwaitValue в F#).
Состояние задачи
Класс Task предоставляет жизненный цикл для асинхронных операций, и этот цикл представлен TaskStatus перечислением. Для поддержки особых случаев типов, производных от Task, а также Task<TResult>, и для разделения процесса построения от процесса планирования, класс Task содержит метод Start. Задачи, созданные общедоступными Task конструкторами, называются холодными задачами, так как они начинают жизненный цикл в непланированном Created состоянии и планируются только при Start вызове этих экземпляров.
Все остальные задачи начинают жизненный цикл в горячем состоянии, что означает, что асинхронные операции, которые они представляют, уже инициированы, и их состояние задачи — это значение перечисления, отличное от TaskStatus.Created. Все задачи, возвращаемые из методов TAP, должны быть активированы. Если метод TAP использует конструктор задачи для создания задачи для возврата, метод TAP должен вызывать Start на Task объекте, прежде чем вернуть его. Потребители метода TAP могут спокойно предполагать, что возвращаемая задача активна и не должны вызывать Start на любом Task, возвращаемом из метода TAP. Вызов Start у активной задачи вызывает исключение InvalidOperationException.
Рекомендации по вопросам времени жизни и концепции "fire-and-forget" после активации задачи см. в разделе "Поддержание асинхронных методов в активном состоянии".
Отмена (необязательно)
В TAP отмена является необязательной как для разработчиков асинхронных методов, так и для потребителей асинхронных методов. Если операция позволяет отмену, она предлагает перегрузку асинхронного метода, который принимает токен отмены (экземпляр CancellationToken). По соглашению параметр называется cancellationToken.
public static 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 static 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 static 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 static 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 аргумент. Если передать 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).
- Implementing шаблон асинхронного программирования на основе задач — описывает, как реализовать TAP тремя способами: с помощью компиляторов C# и Visual Basic в Visual Studio, вручную, или путем комбинации использования компилятора и ручного метода.
- Использование асинхронного шаблона на основе задач — описывает, как можно использовать задачи и обратные вызовы, чтобы обеспечивать ожидание без блокировки.
- Взаимодействие с другими асинхронными шаблонами и типами — описывает, как использовать TAP для реализации модели асинхронного программирования (APM) и асинхронного шаблона на основе событий (EAP).