Асинхронный/ожидание
В компьютерном программировании шаблон async/await — это синтаксическая особенность многих языков программирования которая позволяет структурировать асинхронную неблокирующую , функцию аналогично обычной синхронной функции. Он семантически связан с концепцией сопрограммы и часто реализуется с использованием аналогичных методов и в первую очередь предназначен для предоставления программе возможности выполнять другой код во время ожидания завершения длительной асинхронной задачи, обычно представленной обещаниями или аналогичные структуры данных . Эта функция есть в C# , [1] : 10 C++ , Python , F# , Hack , Julia , Dart , Kotlin , Rust , [2] Nim , [3] JavaScript , Свифт [4] и Зиг . [5]
История [ править ]
В F# добавлены асинхронные рабочие процессы с точками ожидания в версии 2.0 в 2007 году. [6] Это повлияло на механизм async/await, добавленный в C#. [7]
Microsoft впервые выпустила версию C# с async/await в Async CTP (2011). Позже он был официально выпущен в C# 5 (2012). [8] [1] : 10
Ведущий разработчик Haskell Саймон Марлоу создал пакет async в 2012 году. [9]
Python добавил поддержку async/await в версии 3.5 в 2015 году. [10] добавление 2 новых ключевых слов , async
и await
.
В TypeScript добавлена поддержка async/await в версии 1.7 в 2015 году. [11]
В Javascript добавлена поддержка async/await в 2017 году как часть версии ECMAScript 2017 JavaScript.
В Rust добавлена поддержка async/await в версии 1.39.0 в 2019 году с помощью async
ключевое слово и .await
постфиксный оператор, оба они представлены в версии языка 2018 года. [12]
В C++ добавлена поддержка async/await в версии 20 в 2020 году с 3 новыми ключевыми словами. co_return
, co_await
, co_yield
.
Swift добавил поддержку async/await в версии 5.5 в 2021 году, добавив 2 новых ключевых слова. async
и await
. Он был выпущен вместе с конкретной реализацией модели Актера с actor
ключевое слово [13] который использует async/await для обеспечения доступа к каждому актеру извне.
Пример C# [ править ]
Приведенная ниже функция C# , которая загружает ресурс по URI и возвращает длину ресурса, использует этот шаблон async/await:
общественная асинхронная задача <int> FindPageSizeAsync ) вар ( Uri uri )
{
клиент HttpClient = новый ( ;
байт [] данные = ждут клиента . GetByteArrayAsync ( URI );
вернуть данные . Длина ;
}
- Во-первых,
async
ключевое слово указывает C#, что метод является асинхронным, то есть он может использовать произвольное количествоawait
выражения и свяжет результат с обещанием . [1] : 165–168 - значения Тип возвращаемого ,
Task<T>
, является аналогом концепции обещания в C#, и здесь указано, что результирующее значение имеет типint
. - Первое выражение, которое будет выполнено при вызове этого метода, будет
new HttpClient().GetByteArrayAsync(uri)
, [14] : 189–190, 344 [1] : 882 это еще один асинхронный метод, возвращающийTask<byte[]>
. Поскольку этот метод является асинхронным, он не загружает весь пакет данных перед возвратом. Вместо этого он начнет процесс загрузки с использованием неблокирующего механизма (например, фонового потока ) и немедленно вернет неразрешенное, неотклоненное сообщение.Task<byte[]>
к этой функции. - С
await
ключевое слово, прикрепленное кTask
, эта функция немедленно приступит к возвратуTask<int>
вызывающему абоненту, который затем может продолжить обработку по мере необходимости. - Один раз
GetByteArrayAsync()
завершит загрузку, это устранит проблемуTask
он вернулся с загруженными данными. Это вызовет обратный вызов и вызоветFindPageSizeAsync()
продолжить выполнение, присвоив это значениеdata
. - Наконец, метод возвращает
data.Length
, простое целое число, указывающее длину массива. Компилятор интерпретирует это как решениеTask
он вернулся раньше, запустив обратный вызов в вызывающем методе, чтобы что-то сделать с этим значением длины.
Функция, использующая async/await, может использовать столько await
выражения по своему усмотрению, и каждое из них будет обрабатываться одинаково (хотя обещание будет возвращено вызывающей стороне только для первого ожидания, в то время как для каждого другого ожидания будут использоваться внутренние обратные вызовы). Функция также может напрямую хранить объект обещания и сначала выполнять другую обработку (включая запуск других асинхронных задач), откладывая ожидание обещания до тех пор, пока не понадобится его результат. Функции с обещаниями также имеют методы агрегирования обещаний, которые позволяют программе ожидать несколько обещаний одновременно или в каком-то специальном шаблоне (например, в C#). Task.WhenAll()
, [1] : 174–175 [14] : 664–665 который возвращает бесполезное значение Task
это разрешается, когда все задачи в аргументах решены). Многие типы обещаний также имеют дополнительные функции, помимо тех, которые обычно использует шаблон async/await, например, возможность настроить более одного обратного вызова результата или проверить ход особенно длительной задачи.
В конкретном случае C# и во многих других языках с этой языковой функцией шаблон async/await не является основной частью среды выполнения языка, а вместо этого реализуется с помощью лямбда-выражений или продолжений во время компиляции. Например, компилятор C#, скорее всего, преобразует приведенный выше код во что-то вроде следующего, прежде чем переводить его в формат байт-кода IL :
public Task <int> FindPageSizeAsync ) ( ( Uri uri )
{
вар клиент = новый HttpClient ;
Задача < байт [] > dataTask = клиент . GetByteArrayAsync ( URI );
Задача <int> afterDataTask = dataTask . ContinueWith (( originalTask ) => {
return originalTask . Result . length ;
});
вернуться послеDataTask ;
}
По этой причине, если метод интерфейса должен вернуть объект-промис, но сам по себе не требует await
в теле для ожидания выполнения каких-либо асинхронных задач, ему не требуется async
модификатор и вместо этого может напрямую возвращать объект обещания. Например, функция может предоставить обещание, которое немедленно преобразуется в некоторое значение результата (например, в C# Task.FromResult()
[14] : 656 ), или он может просто вернуть обещание другого метода, которое окажется именно тем обещанием, которое необходимо (например, при отсрочке на перегрузку ) .
Однако одно важное предостережение относительно этой функциональности заключается в том, что, хотя код и напоминает традиционный блокирующий код, на самом деле он неблокирующий и потенциально многопоточный, а это означает, что множество промежуточных событий может произойти во время ожидания обещания, предназначенного для await
Разрешить. Например, следующий код всегда успешно реализует модель блокировки без await
, могут возникнуть промежуточные события во время await
и, таким образом, может обнаружить, что общее состояние изменилось из-под него:
вар а = состояние . а ;
вар клиент = новый HttpClient ();
вар данные = ждут клиента . GetByteArrayAsync ( URI );
Отладка . Утверждать ( а == состояние . а ); // Потенциальный сбой, поскольку значение state.a могло быть изменено
// обработчиком потенциально промежуточного события.
вернуть данные . Длина ;
Реализации [ править ]
В F# [ править ]
В 2007 году в F# были добавлены асинхронные рабочие процессы версии 2.0. [15] Асинхронные рабочие процессы реализованы как CE ( выражения вычислений ). Их можно определить без указания какого-либо специального контекста (например, async
в С#). В асинхронных рабочих процессах F# к ключевым словам добавляется значок (!) для запуска асинхронных задач.
Следующая асинхронная функция загружает данные с URL-адреса, используя асинхронный рабочий процесс:
let asyncSumPageSizes ( uris : #seq <URI> ) : Async <int> ( = ! async {
use httpClient = new HttpClient )
let страницы =
uris
|> Seq . карта ( httpClient . GetStringAsync >> Async . AwaitTask )
|> Async . Параллельный
возврат страниц |> Seq . сложить ( веселого аккумулятора ток -> ток . Длина + аккумулятор ) 0
}
В C# [ править ]
В 2012 году C# добавил шаблон async/await в C# версии 5.0, который Microsoft называет асинхронным шаблоном на основе задач (TAP). [16] Асинхронные методы обычно возвращают либо void
, Task
, Task<T>
, [14] : 35 [17] : 546–547 [1] : 22, 182 ValueTask
или ValueTask<T>
. [14] : 651–652 [1] : 182–184 Пользовательский код может определять пользовательские типы, которые асинхронные методы могут возвращать с помощью пользовательских конструкторов асинхронных методов , но это сложный и редкий сценарий. [18] Асинхронные методы, которые возвращают void
предназначены для обработчиков событий ; в большинстве случаев, когда синхронный метод возвращает void
, возвращаясь Task
вместо этого рекомендуется использовать его, поскольку он обеспечивает более интуитивную обработку исключений. [19]
Методы, в которых используются await
должно быть объявлено с помощью async
ключевое слово. В методах, которые имеют возвращаемое значение типа Task<T>
, методы, объявленные с помощью async
должен иметь оператор возврата типа, назначаемого T
вместо Task<T>
; компилятор помещает значение в Task<T>
общий. Также возможно await
методы, которые имеют тип возвращаемого значения Task
или Task<T>
которые объявлены без async
.
Следующий асинхронный метод загружает данные с URL-адреса, используя await
. Поскольку этот метод выдает задачу для каждого URI, прежде чем требовать завершения с помощью await
ключевое слово, ресурсы могут загружаться одновременно, вместо того, чтобы ждать завершения последнего ресурса, прежде чем начинать загрузку следующего.
общественная асинхронная задача <int> SumPageSizesAsync ( IEnumerable <Uri> uris новый ) {
;
вар клиент = HttpClient ( )
целое число = 0 ;
вар loadUriTasks = новый список < задача < байт [] >> ();
foreach ( вар uri в uris )
{
вар loadUriTask = client . GetByteArrayAsync ( URI );
загрузитьUriTasks . Добавить ( loadUriTask );
}
Еогеасп ( вар loadUriTask в loadUriTasks )
{
statusText . Text = $"Найдено {всего} байт..." ;
вар resourcesAsBytes = ждут loadUriTask ;
итого += resourcesAsBytes . Длина ;
}
СтатусТекст . Text = $"Найдено всего {total} байт" ;
возврата общая сумма ;
}
В Python [ править ]
Питон 3.5 (2015 г.) [20] добавлена поддержка async/await, как описано в PEP 492 (написано и реализовано Юрием Селивановым ). [21]
import asyncio
async def main ():
print ( «привет» )
await asyncio . сон ( 1 )
печать ( «мир» )
asyncio . запустить ( основной ()))
В JavaScript [ править ]
Оператор await в JavaScript можно использовать только внутри асинхронной функции или на верхнем уровне модуля . Если параметром является промис , выполнение асинхронной функции возобновится, когда промис будет разрешен (если промис не будет отклонен, в этом случае будет выдана ошибка, которую можно обработать с помощью обычной обработки исключений JavaScript ). Если параметр не является обещанием, сам параметр будет возвращен немедленно. [22]
Многие библиотеки предоставляют объекты-промисы, которые также можно использовать с await, если они соответствуют спецификации собственных промисов JavaScript. Однако промисы из библиотеки jQuery не были совместимы с Promises/A+ до версии jQuery 3.0. [23]
Вот пример (измененный из этого [24] статья):
асинхронная функция createNewDoc () {
let response = await db . почта ({}); // публикуем новый документ
return db . получить ( ответ . идентификатор ); // найти по идентификатору
}
async function main () {
try {
let doc = await createNewDoc ();
консоль . журнал ( документ );
} поймать ( ошибка ) {
console . журнал ( ошибка );
}
}
основной ();
Node.js версии 8 включает утилиту, которая позволяет использовать методы обратного вызова стандартной библиотеки в качестве обещаний. [25]
В C++ [ править ]
В C++ await (названный в C++ co_await) был официально объединен с версией 20 . [26] Поддержка этого, сопрограмм и таких ключевых слов, как co_await
доступны в GCC и MSVC компиляторах , а Clang имеет частичную поддержку.
Стоит отметить, что std::promise и std::future, хотя и могут показаться ожидаемыми объектами, не реализуют ни одного механизма, необходимого для возврата из сопрограмм и ожидания с использованием co_await. Программистам необходимо реализовать ряд публичных функций-членов, таких как await_ready
, await_suspend
, и await_resume
по типу возвращаемого значения, чтобы тип ожидался. Подробности можно найти на cppreference. [27]
#include <iostream>
#include «CustomAwaitableTask.h»
с использованием пространства имен std ;
CustomAwaitableTask <int> int add ( a , int b )
{
int c = a + b ;
co_return c ;
}
CustomAwaitableTask <int> test ) ; )
{
int ret = co_await add ( 1 , 2 (
cout << "возврат" << ret << endl ;
co_return возврат ;
}
Int Main ()
{
авто задача = тест ();
вернуть 0 ;
}
В C [ править ]
Язык C не поддерживает await/async. Некоторые библиотеки сопрограмм, такие как s_task [28] имитируйте ключевые слова 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 );
for ( i = 0 ; i < 4 ; ++ i ) {
printf ( "task_main arg = %p, i = %d \n " , arg , i );
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 " );
вернуть 0 ;
}
В Perl 5 [ править ]
Будущее::AsyncAwait [29] Модуль стал предметом гранта Perl Foundation в сентябре 2018 года. [30]
В Русте [ править ]
7 ноября 2019 года async/await был выпущен в стабильной версии Rust. [31] Асинхронные функции в Rust преобразуют простые функции, возвращающие значения, реализующие черту Future. В настоящее время они реализованы с помощью конечного автомата . [32]
// В Cargo.toml крейта нам нужно `futures = "0.3.0"` в разделе зависимостей,
// чтобы мы могли использовать фьючерсный ящик
extern 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 число = async_add_one ( 5 ). Ждите ;
распечататьлн! ( «5 + 1 = {}» , число );
}
fn main () {
// Создание будущего не запускает выполнение.
пусть будущее = example_task ();
// `Future` выполняется только тогда, когда мы его фактически опрашиваем, в отличие от Javascript.
фьючерс :: исполнитель :: block_on ( будущее );
}
В Swift [ править ]
Свифт 5.5 (2021) [33] добавлена поддержка async/await, как описано в SE-0296. [34]
func getNumber () async throws -> Int {
try await Task . сон ( наносекунды : 1_000_000_000 )
return 42
}
Task {
let first = попробуйте await getNumber ()
let Second = попробуйте await getNumber ()
print ( первый + второй )
}
Преимущества и критика [ править ]
Шаблон async/await особенно привлекателен для разработчиков языков, которые не имеют или не контролируют собственную среду выполнения, поскольку async/await может быть реализован исключительно как преобразование в конечный автомат в компиляторе. [35]
Сторонники утверждают, что асинхронный неблокирующий код можно написать с помощью async/await, который выглядит почти как традиционный синхронный блокирующий код. В частности, утверждалось, что ожидание — лучший способ написания асинхронного кода в передачи сообщений программах близость к блокирующему коду, читаемость и минимальное количество шаблонного кода . ; в частности, в качестве ожидаемых преимуществ были названы [36] В результате async/await позволяет большинству программистов легче рассуждать о своих программах, а await имеет тенденцию способствовать созданию более качественного и надежного неблокирующего кода в приложениях, которые этого требуют. [ сомнительно – обсудить ]
Критики async/await отмечают, что этот шаблон имеет тенденцию вызывать асинхронность и окружающего кода; и что его заразная природа разделяет библиотечные экосистемы языков на синхронные и асинхронные библиотеки и API - проблему, часто называемую «раскраской функций». [37] Альтернативы async/await, которые не страдают от этой проблемы, называются «бесцветными». Go Примеры бесцветного дизайна включают горутины Java и виртуальные потоки . [38]
См. также [ править ]
Ссылки [ править ]
- ^ Перейти обратно: а б с д Это ж г Скит, Джон (23 марта 2019 г.). C# в глубине . Мэннинг. ISBN 978-1617294532 .
- ^ «Анонсируем Rust 1.39.0» . Проверено 07.11.2019 .
- ^ «Вышла версия 0.9.4 — блог Nim» . Проверено 19 января 2020 г.
- ^ «Параллелизм — язык программирования Swift (Swift 5.5)» . docs.swift.org . Проверено 28 сентября 2021 г.
- ^ «Справочник по языку Зиг» .
- ^ Сайм, Дон; Петричек, Томас; Ломов, Дмитрий (2011). «Модель асинхронного программирования F#» . Практические аспекты декларативных языков . Конспекты лекций по информатике. Том. 6539. Спрингер Линк. стр. 175–189. дои : 10.1007/978-3-642-18378-2_15 . ISBN 978-3-642-18377-5 . Проверено 29 апреля 2021 г.
- ^ «Ранняя история F #, HOPL IV» . Цифровая библиотека ACM . Проверено 29 апреля 2021 г.
- ^ Хейлсберг, Андерс. «Андерс Хейлсберг: Знакомство с Async – упрощение асинхронного программирования» . Канал 9 MSDN . Майкрософт . Проверено 5 января 2021 г.
- ^ «async: асинхронно запускать операции ввода-вывода и ждать их результатов» . Хакадж .
- ^ «Что нового в Python 3.5 — документация по Python 3.9.1» . docs.python.org . Проверено 5 января 2021 г.
- ^ Гаурав, Сет (30 ноября 2015 г.). «Анонс TypeScript 1.7» . Типскрипт . Майкрософт . Проверено 5 января 2021 г.
- ^ Мацакис, Нико. «Асинхронное ожидание в стабильной версии Rust! | Блог Rust» . blog.rust-lang.org . Ржавый блог . Проверено 5 января 2021 г.
- ^ «Параллелизм — язык программирования Swift (Swift 5.6)» .
- ^ Перейти обратно: а б с д Это Альбахари, Джозеф (2022). C# 10 в двух словах О'Рейли. ISBN 978-1-098-12195-2 .
- ^ «Знакомство с асинхронными рабочими процессами F#» . 10 октября 2007 г.
- ^ «Асинхронный шаблон на основе задач» . Майкрософт . Проверено 28 сентября 2020 г.
- ^ Прайс, Марк Дж. (2022). C# 8.0 и .NET Core 3.0 — современная кроссплатформенная разработка: создавайте приложения с помощью C#, .NET Core, Entity Framework Core, ASP.NET Core и ML.NET с помощью кода Visual Studio . Пакет. ISBN 978-1-098-12195-2 .
- ^ Тепляков, Сергей (11 января 2018 г.). «Расширение асинхронных методов в C#» . Поддержка разработчиков . Проверено 30 октября 2022 г.
- ^ Стивен Клири, Async/Await - Лучшие практики асинхронного программирования
- ^ «Выпуск Python 3.5.0» .
- ^ «PEP 492 — сопрограммы с синтаксисом async и await» .
- ^ «ожидание — JavaScript (MDN)» . Проверено 2 мая 2017 г.
- ^ «Руководство по обновлению jQuery Core 3.0» . Проверено 2 мая 2017 г.
- ^ «Укрощение асинхронного зверя с помощью ES7» . Проверено 12 ноября 2015 г.
- ^ Foundation, Node.js (30 мая 2017 г.). «Узел v8.0.0 (текущий) — Node.js» . Нод.js.
- ^ «Комитет ISO C++ объявляет, что разработка C++20 теперь завершена» . 25 февраля 2019 г.
- ^ «Сопрограммы (C++20)» .
- ^ «s_task — ожидаемая библиотека сопрограмм для C» . Гитхаб .
- ^ «Future::AsyncAwait — синтаксис отложенной подпрограммы для фьючерсов» .
- ^ «Голосование по грантам в сентябре 2018 г. — The Perl Foundation» . news.perlfoundation.org . Проверено 26 марта 2019 г.
- ^ Мацакис, Нико. «Асинхронное ожидание в стабильной версии Rust!» . Ржавый блог . Проверено 7 ноября 2019 г.
- ^ Опперманн, Филипп. «Асинхронный/Ожидание» . Проверено 28 октября 2020 г.
- ^ «Архивная копия» . Архивировано из оригинала 23 января 2022 г. Проверено 20 декабря 2021 г.
{{cite web}}
: CS1 maint: архивная копия в заголовке ( ссылка ) - ^ «СЭ-0296» . Гитхаб .
- ^ «Асинхронность, часть 3. Как компилятор C# реализует асинхронные функции» .
- ^ Заяц "Никаких ошибок". Восемь способов обработки неблокирующих возвратов в программах передачи сообщений CPPCON, 2018
- ^ «Какого цвета ваша функция?» .
- ^ «Виртуальные темы» .