stringtranslate.com

Асинхронный/ожидание

В программировании , шаблон 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/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 }                                    

В C#

В 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 ( ) )

В JavaScript

Оператор 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 ; }         


В Perl5

Модуль 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 имеет тенденцию продвигать лучший, более надежный неблокирующий код в приложениях, которые этого требуют. [ dubiousdiscussion ]

Критики async/await отмечают, что этот шаблон имеет тенденцию делать окружающий код также асинхронным; и что его заразная природа разделяет экосистемы библиотек языков на синхронные и асинхронные библиотеки и API, проблема, часто называемая «раскрашиванием функций». [36] Альтернативы async/await, которые не страдают от этой проблемы, называются «бесцветными». Примерами бесцветных конструкций являются горутины Go и виртуальные потоки Java . [37]

Смотрите также

Ссылки

  1. ^ abcdefg Скит, Джон (23 марта 2019 г.). C# в глубине . Мэннинг. ISBN 978-1617294532.
  2. ^ "Анонс Rust 1.39.0" . Получено 2019-11-07 .
  3. ^ "Выпущена версия 0.9.4 - блог Nim" . Получено 2020-01-19 .
  4. ^ "Параллелизм — язык программирования Swift (Swift 5.5)". docs.swift.org . Получено 28.09.2021 .
  5. ^ Syme, Don; Petricek, Tomas; Lomov, Dmitry (2011). "Модель асинхронного программирования F#". Практические аспекты декларативных языков . Конспект лекций по информатике. Том 6539. Springer Link. С. 175–189. doi :10.1007/978-3-642-18378-2_15. ISBN 978-3-642-18377-5. Получено 29.04.2021 .
  6. ^ "The Early History of F#, HOPL IV". ACM Digital Library . Получено 29.04.2021 .
  7. ^ Хейлсберг, Андерс. «Андерс Хейлсберг: Знакомство с асинхронностью – упрощение асинхронного программирования». Канал 9 MSDN . Microsoft . Получено 5 января 2021 г. .
  8. ^ "async: Асинхронно выполнять операции ввода-вывода и ждать их результатов". Хакерство .
  9. ^ «Что нового в Python 3.5 — Документация Python 3.9.1». docs.python.org . Получено 5 января 2021 г. .
  10. ^ Гаурав, Сет (30 ноября 2015 г.). «Анонс TypeScript 1.7». TypeScript . Microsoft . Получено 5 января 2021 г. .
  11. ^ Мацакис, Нико. "Async-await в стабильной версии Rust! | Блог Rust". blog.rust-lang.org . Блог Rust . Получено 5 января 2021 г. .
  12. ^ «Параллелизм — язык программирования Swift (Swift 5.6)».
  13. ^ abcde Альбахари, Джозеф (2022). Кратко о C#10 . О'Рейли. ISBN 978-1-098-12195-2.
  14. ^ «Введение в асинхронные рабочие процессы F#». 10 октября 2007 г.
  15. ^ "Task-based asynchronous pattern". Microsoft . Получено 28 сентября 2020 г. .
  16. ^ Прайс, Марк Дж. (2022). C# 8.0 и .NET Core 3.0 — современная кроссплатформенная разработка: создание приложений с C#, .NET Core, Entity Framework Core, ASP.NET Core и ML.NET с использованием Visual Studio Code . Пакет. ISBN 978-1-098-12195-2.
  17. ^ Тепляков, Сергей (2018-01-11). "Расширение асинхронных методов в C#". Поддержка разработчиков . Получено 2022-10-30 .
  18. ^ Стивен Клири, Async/Await — Лучшие практики асинхронного программирования
  19. ^ "Python Release Python 3.5.0".
  20. ^ «PEP 492 – Сопрограммы с синтаксисом async и await».
  21. ^ "await - JavaScript (MDN)" . Получено 2 мая 2017 г. .
  22. ^ "Руководство по обновлению jQuery Core 3.0" . Получено 2 мая 2017 г. .
  23. ^ "Укрощение асинхронного зверя с помощью ES7" . Получено 12 ноября 2015 г.
  24. ^ Foundation, Node.js (30 мая 2017 г.). "Node v8.0.0 (текущая версия) - Node.js". Node.js .
  25. ^ "Комитет ISO C++ объявляет, что разработка C++20 теперь завершена". 25 февраля 2019 г.
  26. ^ "Сопрограммы (C++20)".
  27. ^ "s_task - ожидаемая библиотека сопрограмм для C". GitHub .
  28. ^ "Future::AsyncAwait - синтаксис отложенной подпрограммы для будущих событий".
  29. ^ "Голосование за гранты в сентябре 2018 г. - Фонд Perl". news.perlfoundation.org . Получено 26.03.2019 .
  30. ^ Мацакис, Нико. "Async-await в стабильной версии Rust!". Блог Rust . Получено 7 ноября 2019 г.
  31. ^ Опперманн, Филипп. "Async/Await" . Получено 28 октября 2020 г.
  32. ^ "Архивная копия". Архивировано из оригинала 2022-01-23 . Получено 2021-12-20 .{{cite web}}: CS1 maint: архивная копия как заголовок ( ссылка )
  33. ^ "SE-0296". GitHub .
  34. ^ «Асинхронность. Часть 3. Как компилятор C# реализует асинхронные функции».
  35. ^ «Без ошибок» Харе. Восемь способов обработки неблокируемых возвратов в программах передачи сообщений CPPCON, 2018
  36. ^ «Какого цвета ваша функция?».
  37. ^ «Виртуальные потоки».