Асинхронный/ожидание
В компьютерном программировании шаблон 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]
В Javascript добавлена поддержка async/await в 2017 году как часть версии ECMAScript 2017 JavaScript.
В Rust добавлена поддержка async/await в версии 1.39.0 в 2019 году с помощью async
ключевое слово и .await
постфиксный оператор, оба они представлены в версии языка 2018 года. [11]
В C++ добавлена поддержка async/await в версии 20 в 2020 году с 3 новыми ключевыми словами. co_return
, co_await
, co_yield
.
Swift добавил поддержку async/await в версии 5.5 в 2021 году, добавив 2 новых ключевых слова. async
и await
. Он был выпущен вместе с конкретной реализацией модели Актера с actor
ключевое слово [12] который использует async/await для обеспечения доступа к каждому актеру извне.
Пример С#
[ редактировать ]Приведенная ниже функция C# , которая загружает ресурс по URI и возвращает длину ресурса, использует этот шаблон async/await:
public async Task<int> FindPageSizeAsync(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
, эта функция немедленно приступит к возвратуTask<int>
вызывающему абоненту, который затем может продолжить обработку по мере необходимости. - Один раз
GetByteArrayAsync()
завершит загрузку, это устранит проблемуTask
он вернулся с загруженными данными. Это вызовет обратный вызов и вызоветFindPageSizeAsync()
продолжить выполнение, присвоив это значениеdata
. - Наконец, метод возвращает
data.Length
, простое целое число, указывающее длину массива. Компилятор интерпретирует это как решениеTask
он вернулся раньше, запустив обратный вызов в вызывающем методе, чтобы что-то сделать с этим значением длины.
Функция, использующая async/await, может использовать столько await
выражения по своему усмотрению, и каждое из них будет обрабатываться одинаково (хотя обещание будет возвращено вызывающей стороне только для первого ожидания, тогда как для каждого другого ожидания будут использоваться внутренние обратные вызовы). Функция также может напрямую хранить объект обещания и сначала выполнять другую обработку (включая запуск других асинхронных задач), откладывая ожидание обещания до тех пор, пока не понадобится его результат. Функции с обещаниями также имеют методы агрегирования обещаний, которые позволяют программе ожидать несколько обещаний одновременно или по некоторому специальному шаблону (например, в C#). Task.WhenAll()
, [1] : 174–175 [13] : 664–665 который возвращает бесполезное значение Task
это разрешается, когда все задачи в аргументах решены). Многие типы обещаний также имеют дополнительные функции, помимо тех, которые обычно использует шаблон async/await, например, возможность настроить более одного обратного вызова результата или проверить ход особенно длительной задачи.
В конкретном случае C# и во многих других языках с этой языковой функцией шаблон async/await не является основной частью среды выполнения языка, а вместо этого реализуется с помощью лямбда-выражений или продолжений во время компиляции. Например, компилятор C#, скорее всего, преобразует приведенный выше код во что-то вроде следующего, прежде чем переводить его в формат байт-кода IL :
public Task<int> FindPageSizeAsync(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
модификатор и вместо этого может напрямую возвращать объект обещания. Например, функция может предоставить обещание, которое немедленно преобразуется в некоторое значение результата (например, в C# Task.FromResult()
[13] : 656 ), или он может просто вернуть обещание другого метода, которое окажется именно тем обещанием, которое необходимо (например, при отсрочке на перегрузку ).
Однако одно важное предостережение относительно этой функциональности заключается в том, что, хотя код и напоминает традиционный блокирующий код, на самом деле он неблокирующий и потенциально многопоточный, а это означает, что во время ожидания обещания, предназначенного для await
решить. Например, следующий код всегда успешно реализует модель блокировки без await
, могут возникнуть промежуточные события во время await
и, таким образом, может обнаружить, что общее состояние изменилось из-под него:
var a = state.a;var client = new HttpClient();var data = await client.GetByteArrayAsync(uri);Debug.Assert(a == state.a); // Potential failure, as value of state.a may have been changed // by the handler of potentially intervening event.return data.Length;
Реализации
[ редактировать ]В Фа#
[ редактировать ]В 2007 году в F# были добавлены асинхронные рабочие процессы версии 2.0. [14] Асинхронные рабочие процессы реализованы как CE ( выражения вычислений ). Их можно определить без указания какого-либо специального контекста (например, async
в С#). В асинхронных рабочих процессах 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>
общий. Также возможно 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 = $"Found {total} bytes ..."; var resourceAsBytes = await loadUriTask; total += resourceAsBytes.Length; } statusText.Text = $"Found {total} bytes total"; return total;}
На Python
[ редактировать ]Питон 3.5 (2015 г.) [19] добавлена поддержка async/await, как описано в PEP 492 (написано и реализовано Юрием Селивановым ). [20]
import asyncioasync def main(): print("hello") await asyncio.sleep(1) print("world")asyncio.run(main())
В JavaScript
[ редактировать ]Оператор await в JavaScript можно использовать только внутри асинхронной функции или на верхнем уровне модуля . Если параметром является промис , выполнение асинхронной функции возобновится, когда промис будет разрешен (если промис не будет отклонен, в этом случае будет выдана ошибка, которую можно обработать с помощью обычной обработки исключений JavaScript ). Если параметр не является обещанием, сам параметр будет возвращен немедленно. [21]
Многие библиотеки предоставляют объекты-промисы, которые также можно использовать с await, если они соответствуют спецификации собственных промисов JavaScript. Однако промисы из библиотеки jQuery не были совместимы с Promises/A+ до версии jQuery 3.0. [22]
Вот пример (измененный из этого [23] статья):
async function createNewDoc() { let response = await db.post({}); // post a new doc return db.get(response.id); // find by id}async function main() { try { let doc = await createNewDoc(); console.log(doc); } catch (err) { console.log(err); }}main();
Node.js версии 8 включает утилиту, которая позволяет использовать методы на основе обратного вызова стандартной библиотеки в качестве обещаний. [24]
На С++
[ редактировать ]В C++ await (названный в C++ co_await) был официально объединен с версией 20 . [25] Поддержка этого, сопрограмм и таких ключевых слов, как co_await
доступны в компиляторах GCC и MSVC , а Clang имеет частичную поддержку.
Стоит отметить, что std::promise и std::future, хотя и могут показаться ожидаемыми объектами, не реализуют ни одного механизма, необходимого для возврата из сопрограмм и ожидания с использованием co_await. Программистам необходимо реализовать ряд публичных функций-членов, таких как await_ready
, await_suspend
, и await_resume
по типу возвращаемого значения, чтобы тип ожидался. Подробности можно найти на cppreference. [26]
#include <iostream>#include "CustomAwaitableTask.h"using namespace std;CustomAwaitableTask<int> add(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(); return 0;}
В С
[ редактировать ]Язык C не поддерживает await/async. Некоторые библиотеки сопрограмм, такие как s_task [27] имитируйте ключевые слова await/async с помощью макросов.
#include <stdio.h>#include "s_task.h"// define stack memory for tasksint 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("task %d, delay seconds = %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; // create two sub-tasks 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__); } // wait for the sub-tasks for exit s_task_join(__await__, g_stack0); s_task_join(__await__, g_stack1);}int main(int argc, char* argv) { s_task_init_system(); //create the main task s_task_create(g_stack_main, sizeof(g_stack_main), main_task, (void*)(size_t)argc); s_task_join(__await__, g_stack_main); printf("all task is over\n"); return 0;}
В Перле 5
[ редактировать ]Будущее::AsyncAwait [28] Модуль стал предметом гранта Perl Foundation в сентябре 2018 года. [29]
В ржавчине
[ редактировать ]7 ноября 2019 года async/await был выпущен в стабильной версии Rust. [30] Асинхронные функции в Rust превращают сахар в простые функции, возвращающие значения, реализующие черту Future. В настоящее время они реализованы с помощью конечного автомата . [31]
// In the crate's Cargo.toml, we need `futures = "0.3.0"` in the dependencies section,// so we can use the futures crateextern crate futures; // There is no executor currently in the `std` library.// This desugars to something like// `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() { // Creating the Future does not start the execution. let future = example_task(); // The `Future` only executes when we actually poll it, unlike Javascript. futures::executor::block_on(future);}
В Свифте
[ редактировать ]Свифт 5.5 (2021 г.) [32] добавлена поддержка async/await, как описано в SE-0296. [33]
func getNumber() async throws -> Int { try await Task.sleep(nanoseconds: 1_000_000_000) return 42}Task { let first = try await getNumber() let second = try await getNumber() print(first + second)}
Преимущества и критика
[ редактировать ]Шаблон async/await особенно привлекателен для разработчиков языков, которые не имеют или не контролируют собственную среду выполнения, поскольку async/await может быть реализован исключительно как преобразование в конечный автомат в компиляторе. [34]
Сторонники утверждают, что асинхронный неблокирующий код можно написать с помощью async/await, который выглядит почти как традиционный синхронный блокирующий код. В частности, утверждалось, что ожидание — лучший способ написания асинхронного кода в передачи сообщений программах ; в частности, в качестве ожидаемых преимуществ были названы близость к блокирующему коду, читаемость и минимальное количество шаблонного кода . [35] В результате async/await позволяет большинству программистов легче рассуждать о своих программах, а await имеет тенденцию способствовать созданию более качественного и надежного неблокирующего кода в приложениях, которые этого требуют. [ сомнительно – обсудить ]
Критики async/await отмечают, что этот шаблон также имеет тенденцию вызывать асинхронность окружающего кода; и что его заразная природа разделяет библиотечные экосистемы языков на синхронные и асинхронные библиотеки и API - проблему, часто называемую «раскраской функций». [36] Альтернативы async/await, которые не страдают от этой проблемы, называются «бесцветными». Go Примеры бесцветного дизайна включают горутины Java и виртуальные потоки . [37]
См. также
[ редактировать ]Ссылки
[ редактировать ]- ^ Перейти обратно: а б с д и ж г Скит, Джон (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
- ^ «Какого цвета ваша функция?» .
- ^ «Виртуальные темы» .