Async/await

Last updated

In computer programming, the async/await pattern is a syntactic feature of many programming languages that allows an asynchronous, non-blocking function to be structured in a way similar to an ordinary synchronous function. It is semantically related to the concept of a coroutine and is often implemented using similar techniques, and is primarily intended to provide opportunities for the program to execute other code while waiting for a long-running, asynchronous task to complete, usually represented by promises or similar data structures. The feature is found in C#, [1] :10 C++, Python, F#, Hack, Julia, Dart, Kotlin, Rust, [2] Nim, [3] JavaScript, Swift [4] and Zig. [5]

Contents

History

F# added asynchronous workflows with await points in version 2.0 in 2007. [6] This influenced the async/await mechanism added to C#. [7]

Microsoft first released a version of C# with async/await in the Async CTP (2011). It was later officially released in C# 5 (2012). [8] [1] :10

Haskell lead developer Simon Marlow created the async package in 2012. [9]

Python added support for async/await with version 3.5 in 2015 [10] adding 2 new keywords, async and await.

TypeScript added support for async/await with version 1.7 in 2015. [11]

Javascript added support for async/await in 2017 as part of ECMAScript 2017 JavaScript edition.

Rust added support for async/await with version 1.39.0 in 2019 using the async keyword and the .await postfix operator, both introduced in the 2018 edition of the language. [12]

C++ added support for async/await with version 20 in 2020 with 3 new keywords co_return, co_await, co_yield.

Swift added support for async/await with version 5.5 in 2021, adding 2 new keywords async and await. This was released alongside a concrete implementation of the Actor model with the actor keyword [13] which uses async/await to mediate access to each actor from outside.

Example C#

The C# function below, which downloads a resource from a URI and returns the resource's length, uses this async/await pattern:

publicasyncTask<int>FindPageSizeAsync(Uriuri){varclient=newHttpClient();byte[]data=awaitclient.GetByteArrayAsync(uri);returndata.Length;}

A function using async/await can use as many await expressions as it wants, and each will be handled in the same way (though a promise will only be returned to the caller for the first await, while every other await will utilize internal callbacks). A function can also hold a promise object directly and do other processing first (including starting other asynchronous tasks), delaying awaiting the promise until its result is needed. Functions with promises also have promise aggregation methods that allow the program to await multiple promises at once or in some special pattern (such as C#'s Task.WhenAll(), [1] :174–175 [14] :664–665 which returns a valueless Task that resolves when all of the tasks in the arguments have resolved). Many promise types also have additional features beyond what the async/await pattern normally uses, such as being able to set up more than one result callback or inspect the progress of an especially long-running task.

In the particular case of C#, and in many other languages with this language feature, the async/await pattern is not a core part of the language's runtime, but is instead implemented with lambdas or continuations at compile time. For instance, the C# compiler would likely translate the above code to something like the following before translating it to its IL bytecode format:

publicTask<int>FindPageSizeAsync(Uriuri){varclient=newHttpClient();Task<byte[]>dataTask=client.GetByteArrayAsync(uri);Task<int>afterDataTask=dataTask.ContinueWith((originalTask)=>{returnoriginalTask.Result.Length;});returnafterDataTask;}

Because of this, if an interface method needs to return a promise object, but itself does not require await in the body to wait on any asynchronous tasks, it does not need the async modifier either and can instead return a promise object directly. For instance, a function might be able to provide a promise that immediately resolves to some result value (such as C#'s Task.FromResult() [14] :656), or it may simply return another method's promise that happens to be the exact promise needed (such as when deferring to an overload).

One important caveat of this functionality, however, is that while the code resembles traditional blocking code, the code is actually non-blocking and potentially multithreaded, meaning that many intervening events may occur while waiting for the promise targeted by an await to resolve. For instance, the following code, while always succeeding in a blocking model without await, may experience intervening events during the await and may thus find shared state changed out from under it:

vara=state.a;varclient=newHttpClient();vardata=awaitclient.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.returndata.Length;

Implementations

In F#

In 2007, F# added asynchronous workflows with version 2.0. [15] The asynchronous workflows are implemented as CE (computation expressions). They can be defined without specifying any special context (like async in C#). F# asynchronous workflows append a bang (!) to keywords to start asynchronous tasks.

The following async function downloads data from an URL using an asynchronous workflow:

letasyncSumPageSizes(uris:#seq<Uri>):Async<int>=async{usehttpClient=newHttpClient()let!pages=uris|>Seq.map(httpClient.GetStringAsync>>Async.AwaitTask)|>Async.Parallelreturnpages|>Seq.fold(funaccumulatorcurrent->current.Length+accumulator)0}

In C#

In 2012, C# added the async/await pattern in C# with version 5.0, which Microsoft refers to as the task-based asynchronous pattern (TAP). [16] Async methods usually return either void, Task, Task<T>, [14] :35 [17] :546–547 [1] :22,182ValueTask or ValueTask<T>. [14] :651–652 [1] :182–184 User code can define custom types that async methods can return through custom async method builders but this is an advanced and rare scenario. [18] Async methods that return void are intended for event handlers; in most cases where a synchronous method would return void, returning Task instead is recommended, as it allows for more intuitive exception handling. [19]

Methods that make use of await must be declared with the async keyword. In methods that have a return value of type Task<T>, methods declared with async must have a return statement of type assignable to T instead of Task<T>; the compiler wraps the value in the Task<T> generic. It is also possible to await methods that have a return type of Task or Task<T> that are declared without async.

The following async method downloads data from a URL using await. Because this method issues a task for each uri before requiring completion with the await keyword, the resources can load at the same time instead of waiting for the last resource to finish before starting to load the next.

publicasyncTask<int>SumPageSizesAsync(IEnumerable<Uri>uris){varclient=newHttpClient();inttotal=0;varloadUriTasks=newList<Task<byte[]>>();foreach(varuriinuris){varloadUriTask=client.GetByteArrayAsync(uri);loadUriTasks.Add(loadUriTask);}foreach(varloadUriTaskinloadUriTasks){statusText.Text=$"Found {total} bytes ...";varresourceAsBytes=awaitloadUriTask;total+=resourceAsBytes.Length;}statusText.Text=$"Found {total} bytes total";returntotal;}

In Python

Python 3.5 (2015) [20] has added support for async/await as described in PEP 492 (written and implemented by Yury Selivanov). [21]

importasyncioasyncdefmain():print("hello")awaitasyncio.sleep(1)print("world")asyncio.run(main())

In JavaScript

The await operator in JavaScript can only be used from inside an async function or at the top level of a module. If the parameter is a promise, execution of the async function will resume when the promise is resolved (unless the promise is rejected, in which case an error will be thrown that can be handled with normal JavaScript exception handling). If the parameter is not a promise, the parameter itself will be returned immediately. [22]

Many libraries provide promise objects that can also be used with await, as long as they match the specification for native JavaScript promises. However, promises from the jQuery library were not Promises/A+ compatible until jQuery 3.0. [23]

Here's an example (modified from this [24] article):

asyncfunctioncreateNewDoc(){letresponse=awaitdb.post({});// post a new docreturndb.get(response.id);// find by id}asyncfunctionmain(){try{letdoc=awaitcreateNewDoc();console.log(doc);}catch(err){console.log(err);}}main();

Node.js version 8 includes a utility that enables using the standard library callback-based methods as promises. [25]

In C++

In C++, await (named co_await in C++) has been officially merged into version 20. [26] Support for it, coroutines, and the keywords such as co_await are available in GCC and MSVC compilers while Clang has partial support.

It is worth noting that std::promise and std::future, although it would seem that they would be awaitable objects, implement none of the machinery required to be returned from coroutines and be awaited using co_await. Programmers must implement a number of public member functions, such as await_ready, await_suspend, and await_resume on the return type in order for the type to be awaited on. Details can be found on cppreference. [27]

#include<iostream>#include"CustomAwaitableTask.h"usingnamespacestd;CustomAwaitableTask<int>add(inta,intb){intc=a+b;co_returnc;}CustomAwaitableTask<int>test(){intret=co_awaitadd(1,2);cout<<"return "<<ret<<endl;co_returnret;}intmain(){autotask=test();return0;}

In C

The C language does not support await/async. Some coroutine libraries such as s_task [28] simulate the keywords await/async with macros.

#include<stdio.h>#include"s_task.h"// define stack memory for tasksintg_stack_main[64*1024/sizeof(int)];intg_stack0[64*1024/sizeof(int)];intg_stack1[64*1024/sizeof(int)];voidsub_task(__async__,void*arg){inti;intn=(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__);}}voidmain_task(__async__,void*arg){inti;// create two sub-taskss_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 exits_task_join(__await__,g_stack0);s_task_join(__await__,g_stack1);}intmain(intargc,char*argv){s_task_init_system();//create the main tasks_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");return0;}


In Perl 5

The Future::AsyncAwait [29] module was the subject of a Perl Foundation grant in September 2018. [30]

In Rust

On November 7, 2019, async/await was released on the stable version of Rust. [31] Async functions in Rust desugar to plain functions that return values that implement the Future trait. Currently they are implemented with a finite state machine. [32]

// In the crate's Cargo.toml, we need `futures = "0.3.0"` in the dependencies section,// so we can use the futures crateexterncratefutures;// There is no executor currently in the `std` library.// This desugars to something like// `fn async_add_one(num: u32) -> impl Future<Output = u32>`asyncfnasync_add_one(num: u32)-> u32{num+1}asyncfnexample_task(){letnumber=async_add_one(5).await;println!("5 + 1 = {}",number);}fnmain(){// Creating the Future does not start the execution.letfuture=example_task();// The `Future` only executes when we actually poll it, unlike Javascript.futures::executor::block_on(future);}

In Swift

Swift 5.5 (2021) [33] added support for async/await as described in SE-0296. [34]

funcgetNumber()asyncthrows->Int{tryawaitTask.sleep(nanoseconds:1_000_000_000)return42}Task{letfirst=tryawaitgetNumber()letsecond=tryawaitgetNumber()print(first+second)}

Benefits and criticisms

The async/await pattern is especially attractive to language designers of languages that do not have or control their own runtime, as async/await can be implemented solely as a transformation to a state machine in the compiler. [35]

Supporters claim that asynchronous, non-blocking code can be written with async/await that looks almost like traditional synchronous, blocking code. In particular, it has been argued that await is the best way of writing asynchronous code in message-passing programs; in particular, being close to blocking code, readability and the minimal amount of boilerplate code were cited as await benefits. [36] As a result, async/await makes it easier for most programmers to reason about their programs, and await tends to promote better, more robust non-blocking code in applications that require it.[ dubious ]

Critics of async/await note that the pattern tends to cause surrounding code to be asynchronous too; and that its contagious nature splits languages' library ecosystems between synchronous and asynchronous libraries and APIs, an issue often referred to as "function coloring". [37] Alternatives to async/await that do not suffer from this issue are called "colorless". Examples of colorless designs include Go's goroutines and Java's virtual threads. [38]

See also

Related Research Articles

In computer science, control flow is the order in which individual statements, instructions or function calls of an imperative program are executed or evaluated. The emphasis on explicit control flow distinguishes an imperative programming language from a declarative programming language.

<span class="mw-page-title-main">F Sharp (programming language)</span> Microsoft programming language

F# is a general-purpose, high-level, strongly typed, multi-paradigm programming language that encompasses functional, imperative, and object-oriented programming methods. It is most often used as a cross-platform Common Language Infrastructure (CLI) language on .NET, but can also generate JavaScript and graphics processing unit (GPU) code.

Coroutines are computer program components that allow execution to be suspended and resumed, generalizing subroutines for cooperative multitasking. Coroutines are well-suited for implementing familiar program components such as cooperative tasks, exceptions, event loops, iterators, infinite lists and pipes.

<span class="mw-page-title-main">C syntax</span> Set of rules defining correctly structured programs

The syntax of the C programming language is the set of rules governing writing of software in C. It is designed to allow for programs that are extremely terse, have a close relationship with the resulting object code, and yet provide relatively high-level data abstraction. C was the first widely successful high-level language for portable operating-system development.

<span class="mw-page-title-main">Pointer (computer programming)</span> Object which stores memory addresses in a computer program

In computer science, a pointer is an object in many programming languages that stores a memory address. This can be that of another value located in computer memory, or in some cases, that of memory-mapped computer hardware. A pointer references a location in memory, and obtaining the value stored at that location is known as dereferencing the pointer. As an analogy, a page number in a book's index could be considered a pointer to the corresponding page; dereferencing such a pointer would be done by flipping to the page with the given page number and reading the text found on that page. The actual format and content of a pointer variable is dependent on the underlying computer architecture.

In computer science, a union is a value that may have any of several representations or formats within the same position in memory; that consists of a variable that may hold such a data structure. Some programming languages support special data types, called union types, to describe such values and variables. In other words, a union type definition will specify which of a number of permitted primitive types may be stored in its instances, e.g., "float or long integer". In contrast with a record, which could be defined to contain both a float and an integer; in a union, there is only one value at any given time.

In computer science, a generator is a routine that can be used to control the iteration behaviour of a loop. All generators are also iterators. A generator is very similar to a function that returns an array, in that a generator has parameters, can be called, and generates a sequence of values. However, instead of building an array containing all the values and returning them all at once, a generator yields the values one at a time, which requires less memory and allows the caller to get started processing the first few values immediately. In short, a generator looks like a function but behaves like an iterator.

This article compares two programming languages: C# with Java. While the focus of this article is mainly the languages and their features, such a comparison will necessarily also consider some features of platforms and libraries. For a more detailed comparison of the platforms, see Comparison of the Java and .NET platforms.

In computer programming, an entry point is the place in a program where the execution of a program begins, and where the program has access to command line arguments.

The computer programming languages C and Pascal have similar times of origin, influences, and purposes. Both were used to design their own compilers early in their lifetimes. The original Pascal definition appeared in 1969 and a first compiler in 1970. The first version of C appeared in 1972.

In computer science, future, promise, delay, and deferred refer to constructs used for synchronizing program execution in some concurrent programming languages. They describe an object that acts as a proxy for a result that is initially unknown, usually because the computation of its value is not yet complete.

Concurrent computing is a form of computing in which several computations are executed concurrently—during overlapping time periods—instead of sequentially—with one completing before the next starts.

sizeof is a unary operator in the programming languages C and C++. It generates the storage size of an expression or a data type, measured in the number of char-sized units. Consequently, the construct sizeof (char) is guaranteed to be 1. The actual number of bits of type char is specified by the preprocessor macro CHAR_BIT, defined in the standard include file limits.h. On most modern computing platforms this is eight bits. The result of sizeof has an unsigned integer type that is usually denoted by size_t.

The C and C++ programming languages are closely related but have many significant differences. C++ began as a fork of an early, pre-standardized C, and was designed to be mostly source-and-link compatible with C compilers of the time. Due to this, development tools for the two languages are often integrated into a single product, with the programmer able to specify C or C++ as their source language.

In computer programming, an anonymous function is a function definition that is not bound to an identifier. Anonymous functions are often arguments being passed to higher-order functions or used for constructing the result of a higher-order function that needs to return a function. If the function is only used once, or a limited number of times, an anonymous function may be syntactically lighter than using a named function. Anonymous functions are ubiquitous in functional programming languages and other languages with first-class functions, where they fulfil the same role for the function type as literals do for other data types.

In software engineering, a fluent interface is an object-oriented API whose design relies extensively on method chaining. Its goal is to increase code legibility by creating a domain-specific language (DSL). The term was coined in 2005 by Eric Evans and Martin Fowler.

This article describes the syntax of the C# programming language. The features described are compatible with .NET Framework and Mono.

In computer programming, variable shadowing occurs when a variable declared within a certain scope has the same name as a variable declared in an outer scope. At the level of identifiers, this is known as name masking. This outer variable is said to be shadowed by the inner variable, while the inner identifier is said to mask the outer identifier. This can lead to confusion, as it may be unclear which variable subsequent uses of the shadowed variable name refer to, which depends on the name resolution rules of the language

Swift is a high-level general-purpose, multi-paradigm, compiled programming language created by Chris Lattner in 2010 for Apple Inc. and maintained by the open-source community. Swift compiles to machine code, as it is an LLVM-based compiler. Swift was first released in June 2014, and the Swift toolchain has shipped in Xcode since version 6, released in 2014.

<span class="mw-page-title-main">Nim (programming language)</span> Programming language

Nim is a general-purpose, multi-paradigm, statically typed, compiled high-level systems programming language, designed and developed by a team around Andreas Rumpf. Nim is designed to be "efficient, expressive, and elegant", supporting metaprogramming, functional, message passing, procedural, and object-oriented programming styles by providing several features such as compile time code generation, algebraic data types, a foreign function interface (FFI) with C, C++, Objective-C, and JavaScript, and supporting compiling to those same languages as intermediate representations.

References

  1. 1 2 3 4 5 6 7 Skeet, Jon (23 March 2019). C# in Depth. Manning. ISBN   978-1617294532.
  2. "Announcing Rust 1.39.0" . Retrieved 2019-11-07.
  3. "Version 0.9.4 released - Nim blog" . Retrieved 2020-01-19.
  4. "Concurrency — The Swift Programming Language (Swift 5.5)". docs.swift.org. Retrieved 2021-09-28.
  5. "Zig Language Reference".
  6. Syme, Don; Petricek, Tomas; Lomov, Dmitry (2011). "The F# Asynchronous Programming Model". Practical Aspects of Declarative Languages. Lecture Notes in Computer Science. Vol. 6539. Springer Link. pp. 175–189. doi:10.1007/978-3-642-18378-2_15. ISBN   978-3-642-18377-5 . Retrieved 2021-04-29.
  7. "The Early History of F#, HOPL IV". ACM Digital Library. Retrieved 2021-04-29.
  8. Hejlsberg, Anders. "Anders Hejlsberg: Introducing Async – Simplifying Asynchronous Programming". Channel 9 MSDN. Microsoft. Retrieved 5 January 2021.
  9. "async: Run IO operations asynchronously and wait for their results". Hackage.
  10. "What's New In Python 3.5 — Python 3.9.1 documentation". docs.python.org. Retrieved 5 January 2021.
  11. Gaurav, Seth (30 November 2015). "Announcing TypeScript 1.7". TypeScript. Microsoft. Retrieved 5 January 2021.
  12. Matsakis, Niko. "Async-await on stable Rust! | Rust Blog". blog.rust-lang.org. Rust Blog. Retrieved 5 January 2021.
  13. "Concurrency — the Swift Programming Language (Swift 5.6)".
  14. 1 2 3 4 5 Albahari, Joseph (2022). C# 10 in a Nutshell. O'Reilly. ISBN   978-1-098-12195-2.
  15. "Introducing F# Asynchronous Workflows". 10 October 2007.
  16. "Task-based asynchronous pattern". Microsoft. Retrieved 28 September 2020.
  17. Price, Mark J. (2022). C# 8.0 and .NET Core 3.0 – Modern Cross-Platform Development: Build Applications with C#, .NET Core, Entity Framework Core, ASP.NET Core, and ML.NET Using Visual Studio Code. Packt. ISBN   978-1-098-12195-2.
  18. Tepliakov, Sergey (2018-01-11). "Extending the async methods in C#". Developer Support. Retrieved 2022-10-30.
  19. Stephen Cleary, Async/Await - Best Practices in Asynchronous Programming
  20. "Python Release Python 3.5.0".
  21. "PEP 492 – Coroutines with async and await syntax".
  22. "await - JavaScript (MDN)" . Retrieved 2 May 2017.
  23. "jQuery Core 3.0 Upgrade Guide" . Retrieved 2 May 2017.
  24. "Taming the asynchronous beast with ES7" . Retrieved 12 November 2015.
  25. Foundation, Node.js (30 May 2017). "Node v8.0.0 (Current) - Node.js". Node.js.
  26. "ISO C++ Committee announces that C++20 design is now feature complete". 25 February 2019.
  27. "Coroutines (C++20)".
  28. "s_task - awaitable coroutine library for C". GitHub .
  29. "Future::AsyncAwait - deferred subroutine syntax for futures".
  30. "September 2018 Grant Votes - The Perl Foundation". news.perlfoundation.org. Retrieved 2019-03-26.
  31. Matsakis, Niko. "Async-await on stable Rust!". Rust Blog. Retrieved 7 November 2019.
  32. Oppermann, Philipp. "Async/Await" . Retrieved 28 October 2020.
  33. "Archived copy". Archived from the original on 2022-01-23. Retrieved 2021-12-20.{{cite web}}: CS1 maint: archived copy as title (link)
  34. "SE-0296". GitHub .
  35. "Async Part 3 - How the C# compiler implements async functions".
  36. 'No Bugs' Hare. Eight ways to handle non-blocking returns in message-passing programs CPPCON, 2018
  37. "What Color is Your Function?".
  38. "Virtual Threads".