В программировании , шаблон async/await является синтаксической особенностью многих языков программирования , которая позволяет асинхронной , неблокирующей функции быть структурированной таким же образом , как и обычная синхронная функция. Он семантически связан с концепцией сопрограммы и часто реализуется с использованием похожих методов, и в первую очередь предназначен для предоставления возможности программе выполнять другой код во время ожидания завершения длительной , асинхронной задачи, обычно представленной обещаниями или аналогичными структурами данных . Эта особенность встречается в C# , [1] : 10 C++ , Python , F# , Hack , Julia , Dart , Kotlin , Rust , [2] Nim , [3] JavaScript и Swift . [4]
В F# в версии 2.0 в 2007 году были добавлены асинхронные рабочие процессы с точками ожидания. [5] Это повлияло на механизм async/await, добавленный в C#. [6]
Microsoft впервые выпустила версию C# с async/await в Async CTP (2011). Позднее она была официально выпущена в C# 5 (2012). [7] [1] : 10
Ведущий разработчик Haskell Саймон Марлоу создал пакет async в 2012 году. [8]
В Python добавлена поддержка async/await с версии 3.5 в 2015 году [9], где добавлены 2 новых ключевых слова , async
и await
.
TypeScript добавил поддержку async/await в версии 1.7 в 2015 году. [10]
Поддержка async/await была добавлена в Javascript в 2017 году как часть ECMAScript 2017 JavaScript edition.
В Rust добавлена поддержка async/await с версии 1.39.0 в 2019 году с использованием async
ключевого слова и .await
постфиксного оператора, которые были введены в версии языка 2018 года. [11]
В C++ добавлена поддержка async/await с версии 20 в 2020 году с тремя новыми ключевыми словами co_return
, co_await
, co_yield
.
Swift добавил поддержку async/await в версии 5.5 в 2021 году, добавив 2 новых ключевых слова async
и await
. Это было выпущено вместе с конкретной реализацией модели Actor с actor
ключевым словом [12] , которая использует async/await для посредничества в доступе к каждому актору извне.
Функция C# ниже, которая загружает ресурс из URI и возвращает длину ресурса, использует этот шаблон async/await:
public async Task < int > FindSizeOfPageAsync ( Uri uri ) { var client = new HttpClient (); byte [] data = await client . GetByteArrayAsync ( uri ); return data . Length ; }
async
ключевое слово указывает C#, что метод является асинхронным, то есть он может использовать произвольное количество await
выражений и свяжет результат с обещанием . [1] : 165–168 Task<T>
, является аналогом концепции обещания в C# и здесь указывается, что имеет результирующее значение типа int
.new HttpClient().GetByteArrayAsync(uri)
, [13] : 189–190, 344 [1] : 882 , что является другим асинхронным методом, возвращающим Task<byte[]>
. Поскольку этот метод асинхронный, он не будет загружать весь пакет данных перед возвратом. Вместо этого он начнет процесс загрузки, используя неблокирующий механизм (например, фоновый поток ), и немедленно вернет неразрешенный, неотклоненный Task<byte[]>
этой функции.await
ключевое слово к Task
, эта функция немедленно приступит к возврату a Task<int>
вызывающей стороне, которая затем может продолжить другую обработку по мере необходимости.GetByteArrayAsync()
завершения загрузки он разрешит Task
возвращенный им с загруженными данными. Это вызовет обратный вызов и заставит FindPageSizeAsync()
продолжить выполнение, присвоив это значение data
.data.Length
, простое целое число, указывающее длину массива. Компилятор интерпретирует это как разрешение того, что Task
он вернул ранее, запуская обратный вызов в вызывающем методе, чтобы что-то сделать с этим значением длины.Функция, использующая async/await, может использовать столько await
выражений, сколько захочет, и каждое будет обработано одинаково (хотя обещание будет возвращено вызывающей стороне только для первого await, в то время как каждое другое await будет использовать внутренние обратные вызовы). Функция также может напрямую содержать объект обещания и сначала выполнять другую обработку (включая запуск других асинхронных задач), откладывая ожидание обещания до тех пор, пока не понадобится его результат. Функции с обещаниями также имеют методы агрегации обещаний, которые позволяют программе ожидать несколько обещаний одновременно или в каком-то специальном шаблоне (например, C#'s Task.WhenAll()
, [1] : 174–175 [13] : 664–665 , который возвращает значение Task
, которое разрешается, когда все задачи в аргументах разрешены). Многие типы обещаний также имеют дополнительные возможности, выходящие за рамки того, что обычно использует шаблон async/await, например, возможность настроить более одного обратного вызова результата или проверить ход выполнения особенно длительной задачи.
В частном случае C# и во многих других языках с этой языковой функцией шаблон async/await не является основной частью среды выполнения языка, а вместо этого реализуется с помощью лямбд или продолжений во время компиляции. Например, компилятор C#, скорее всего, переведет приведенный выше код во что-то вроде следующего, прежде чем перевести его в формат байт-кода IL :
public Task < int > FindSizeOfPageAsync ( Uri uri ) { var client = new HttpClient (); Task < byte [] > dataTask = client . GetByteArrayAsync ( uri ); Task < int > afterDataTask = dataTask . ContinueWith ( ( originalTask ) => { return originalTask . Result . Length ; }); return afterDataTask ; }
Из-за этого, если метод интерфейса должен возвращать объект обещания, но сам не требует await
в теле ожидания каких-либо асинхронных задач, ему async
также не нужен модификатор, и он может вместо этого напрямую возвращать объект обещания. Например, функция может предоставить обещание, которое немедленно разрешается в некоторое результирующее значение (например, Task.FromResult()
[13] : 656 в C# ), или она может просто вернуть обещание другого метода, которое оказывается именно тем обещанием, которое нужно (например, при отсрочке до перегрузки).
Однако одно важное предостережение относительно этой функциональности заключается в том, что, хотя код напоминает традиционный код блокировки, на самом деле он неблокируемый и потенциально многопоточный, что означает, что во время ожидания обещания, на которое нацелено await
разрешение, может произойти множество промежуточных событий. Например, следующий код, хотя всегда успешно выполняется в модели блокировки без await
, может столкнуться с промежуточными событиями во время await
и, таким образом, обнаружить, что общее состояние изменилось из-под него:
var a = state . a ; var client = new HttpClient (); var data = await client . GetByteArrayAsync ( uri ); Debug . Assert ( a == state . a ); // Возможная ошибка, так как значение state.a могло быть изменено // обработчиком потенциально промежуточного события. return data . Length ;
В 2007 году в F# были добавлены асинхронные рабочие процессы с версией 2.0. [14] Асинхронные рабочие процессы реализованы как CE ( выражения вычислений ). Их можно определить без указания какого-либо специального контекста (как async
в C#). Асинхронные рабочие процессы F# добавляют восклицательный знак (!) к ключевым словам для запуска асинхронных задач.
Следующая асинхронная функция загружает данные с URL-адреса, используя асинхронный рабочий процесс:
let asyncSumPageSizes ( uris : #seq <Uri> ) : Async <int> = async { use httpClient = new HttpClient ( ) let ! pages = uris | > Seq.map ( httpClient.GetStringAsync >> Async.AwaitTask ) | > Async.Parallel return pages | > Seq.fold ( fun accumulator current - > current.Length + accumulator ) 0 }
В 2012 году в C# был добавлен шаблон async/await в C# с версией 5.0, который Microsoft называет асинхронным шаблоном на основе задач (TAP). [15] Асинхронные методы обычно возвращают либо void
, Task
, Task<T>
, [13] : 35 [16] : 546–547 [1] : 22, 182 ValueTask
или ValueTask<T>
. [13] : 651–652 [1] : 182–184 Пользовательский код может определять пользовательские типы, которые асинхронные методы могут возвращать через пользовательские конструкторы асинхронных методов , но это сложный и редкий сценарий. [17] Асинхронные методы, которые возвращают, void
предназначены для обработчиков событий ; в большинстве случаев, когда синхронный метод вернет void
, рекомендуется возвращать Task
вместо этого , так как это обеспечивает более интуитивную обработку исключений. [18]
Методы, которые используют, await
должны быть объявлены с async
ключевым словом. В методах, которые имеют возвращаемое значение типа Task<T>
, методы, объявленные с , async
должны иметь оператор возврата типа, назначаемого T
вместо Task<T>
; компилятор оборачивает значение в Task<T>
generic. Также возможно, что await
методы, которые имеют возвращаемый тип Task
или Task<T>
, объявлены без async
.
Следующий асинхронный метод загружает данные с URL-адреса с помощью await
. Поскольку этот метод выдает задачу для каждого URI, прежде чем потребовать завершения с await
ключевым словом, ресурсы могут загружаться одновременно, вместо того чтобы ждать завершения последнего ресурса, прежде чем начинать загрузку следующего.
public async Task < int > SumPageSizesAsync ( IEnumerable < Uri > uris ) { var client = new HttpClient (); int total = 0 ; var loadUriTasks = new List < Task < byte [] >> (); foreach ( var uri in uris ) { var loadUriTask = client . GetByteArrayAsync ( uri ); loadUriTasks . Add ( loadUriTask ); } foreach ( var loadUriTask in loadUriTasks ) { statusText . Text = $"Найдено {total} байт ..." ; var resourceAsBytes = await loadUriTask ; total += resourceAsBytes . Length ; } statusText . Text = $"Найдено {total} байт всего" ; вернуть итого ; }
В Python 3.5 (2015) [19] добавлена поддержка async/await, как описано в PEP 492 (написано и реализовано Юрием Селивановым). [20]
импорт асинхронныйasync def main (): print ( "привет" ) await asyncio.sleep ( 1 ) print ( " мир " ) asyncio.run ( main ( ) )
Оператор await в JavaScript может использоваться только изнутри асинхронной функции или на верхнем уровне модуля . Если параметр является обещанием , выполнение асинхронной функции возобновится после разрешения обещания (если только обещание не будет отклонено, в этом случае будет выдана ошибка, которую можно обработать с помощью обычной обработки исключений JavaScript ). Если параметр не является обещанием, сам параметр будет возвращен немедленно. [21]
Многие библиотеки предоставляют объекты promise, которые также могут использоваться с await, если они соответствуют спецификации для собственных обещаний JavaScript. Однако обещания из библиотеки jQuery не были совместимы с Promises/A+ до jQuery 3.0. [22]
Вот пример (измененный вариант этой статьи [23] ):
async function createNewDoc () { let response = await db . post ({}); // опубликовать новый документ return db . get ( response . id ); // найти по идентификатору } асинхронная функция main ( ) { try { let doc = await createNewDoc (); console.log ( doc ); } catch ( err ) { console.log ( err ) ; } } main ( ) ;
Node.js версии 8 включает утилиту, которая позволяет использовать методы обратного вызова стандартной библиотеки в качестве обещаний. [24]
В C++ await (называемый co_await в C++) был официально объединен с версией 20. [ 25] Поддержка этого await, сопрограмм и таких ключевых слов, как , co_await
доступна в компиляторах GCC и MSVC , в то время как Clang имеет частичную поддержку.
Стоит отметить, что std::promise и std::future, хотя и кажутся ожидаемыми объектами, не реализуют ни одного механизма, необходимого для возврата из сопрограмм и ожидания с помощью co_await. Программисты должны реализовать ряд публичных функций-членов, таких как await_ready
, await_suspend
, и await_resume
для возвращаемого типа, чтобы тип ожидался. Подробности можно найти на cppreference. [26]
#include <iostream> #include "CustomAwaitableTask.h" с использованием пространства имен std ; CustomAwaitableTask < int > добавить ( int a , int b ) { int c = a + b ; co_return c ; } CustomAwaitableTask < int > test () { int ret = co_await add ( 1 , 2 ); cout << "return " << ret << endl ; co_return ret ; } int main () { auto task = test (); вернуть 0 ; }
Язык C не поддерживает await/async. Некоторые библиотеки сопрограмм, такие как s_task [27], имитируют ключевые слова await/async с помощью макросов.
#include <stdio.h> #include "s_task.h" // определяем стековую память для задач int g_stack_main [ 64 * 1024 / sizeof ( int )]; int g_stack0 [ 64 * 1024 / sizeof ( int )]; int g_stack1 [ 64 * 1024 / sizeof ( int )]; void sub_task ( __async__ , void * arg ) { int i ; int n = ( int )( size_t ) arg ; for ( i = 0 ; i < 5 ; ++ i ) { printf ( "задача %d, задержка секунд = %d, i = %d \n " , n , n , i ); s_task_msleep ( __await__ , n * 1000 ); //s_task_yield(__await__); } } void main_task ( __async__ , void * arg ) { int i ; // создаем две подзадачи s_task_create ( g_stack0 , sizeof ( g_stack0 ), sub_task , ( void * ) 1 ); s_task_create ( g_stack1 , sizeof ( g_stack1 ), sub_task , ( void * ) 2 ); для ( я = 0 ; я < 4 ; ++ я ) { printf ( "task_main arg = %p, я = %d \n " , arg , я ); s_task_yield ( __await__ ); } // ждем подзадач для выхода s_task_join ( __await__ , g_stack0 ); s_task_join ( __await__ , g_stack1 ); } int main ( int argc , char * argv ) { s_task_init_system (); //создаем основную задачу s_task_create ( g_stack_main , sizeof ( g_stack_main ), main_task , ( void * )( size_t ) argc ); s_task_join ( __await__ , g_stack_main ); printf ( "все задачи выполнены \n " ); return 0 ; }
Модуль Future::AsyncAwait [28] стал предметом гранта Perl Foundation в сентябре 2018 года. [29]
7 ноября 2019 года async/await был выпущен в стабильной версии Rust. [30] Асинхронные функции в Rust преобразуются в простые функции, возвращающие значения, реализующие черту Future. В настоящее время они реализованы с помощью конечного автомата . [31]
// В Cargo.toml ящика нам нужно `futures = "0.3.0"` в разделе зависимостей, // чтобы мы могли использовать ящик futuresextern crate futures ; // В настоящее время в библиотеке `std` нет исполнителя. // Это преобразуется в нечто вроде // `fn async_add_one(num: u32) -> impl Future<Output = u32>` async fn async_add_one ( num : u32 ) -> u32 { num + 1 } async fn example_task () { let number = async_add_one ( 5 ) .await ; println! ( "5 + 1 = {}" , number ); } fn main () { // Создание Future не запускает выполнение. let future = example_task (); // `Future` выполняется только тогда, когда мы его фактически опрашиваем, в отличие от Javascript. futures :: executor :: block_on ( future ); }
Swift 5.5 (2021) [32] добавила поддержку async/await, как описано в SE-0296. [33]
func getNumber ( ) асинхронные вызовы - > Int { try await Task.sleep ( наносекунды : 1_000_000_000 ) return 42 } Задача { пусть первая = попробуйте подождать getNumber () пусть вторая = попробуйте подождать getNumber () распечатать ( первый + второй ) }
Шаблон async/await особенно привлекателен для разработчиков языков, которые не имеют или не контролируют собственную среду выполнения, поскольку async/await может быть реализован исключительно как преобразование в конечный автомат в компиляторе. [34]
Сторонники утверждают, что асинхронный, неблокирующий код может быть написан с помощью async/await, который выглядит почти как традиционный синхронный, блокирующий код. В частности, утверждается, что await — лучший способ написания асинхронного кода в программах передачи сообщений ; в частности, близость к блокирующему коду, читаемость и минимальное количество шаблонного кода были указаны как преимущества await. [35] В результате async/await облегчает большинству программистов рассуждать о своих программах, а await имеет тенденцию продвигать лучший, более надежный неблокирующий код в приложениях, которые этого требуют. [ dubious – discussion ]
Критики async/await отмечают, что этот шаблон имеет тенденцию делать окружающий код также асинхронным; и что его заразная природа разделяет экосистемы библиотек языков на синхронные и асинхронные библиотеки и API, проблема, часто называемая «раскрашиванием функций». [36] Альтернативы async/await, которые не страдают от этой проблемы, называются «бесцветными». Примерами бесцветных конструкций являются горутины Go и виртуальные потоки Java . [37]
{{cite web}}
: CS1 maint: архивная копия как заголовок ( ссылка )