Async/await
In computer programming, the async/await pattern is a syntactic feature of many programming languages that allows an asynchronous, non-blocking function call to be structured in a way similar to an ordinary synchronous function call. It is semantically very similar to the concept of a coroutine and is in fact often implemented as such, but has a different semantic meaning in being intended to provide opportunities for the program to execute other code while waiting for a long-running, asynchronous task to complete. The feature is found in C# 5.0, Python 3.5, Hack, Dart, Kotlin 1.1, and JavaScript, with some experimental work in extensions, beta versions, and particular implementations of Scala[1], Rust[2], and C++.
The async/await pattern is typically based on the concept of a promise, a concurrent data structure that represents a result to be provided in the future. At its most basic level, a promise is created by an asynchronous function and a reference to it is held by the function's caller, and when the asynchronous action is complete, the function will irreversibly mutate the promise structure to contain the result, and will notify the caller via a callback that the promise has been resolved with this value. The async/await pattern is one particular use of this promise structure, where the notification callback is simply the code that follows the await expression. The async/await pattern also simplifies error handling: when an asynchronous function fails, it will typically mutate its promise into a rejected state with an object provided as the reason for failure, and an await expression that encounters this situation will cause the object to be thrown as an exception, thus utilizing the language's exception-handling control flow instead of requiring the user to handle the issue manually. Methods that use the async/await pattern are also made to return and manipulate promises themselves.
Example C#
The C# function below, which downloads a resource from a URI and returns the resource's length, uses this async/await pattern:
public async Task<int> FindPageSize(Uri uri)
{
byte[] data = await new WebClient().DownloadDataTaskAsync(uri);
return data.Length;
}
- First, the
async
keyword indicates to C# that the method is asynchronous, meaning that it will use one or moreawait
expressions and will bind. - The return type,
Task<T>
, is C#'s analogue to the concept of a promise, and here is indicated to have a result value of typeint
. - The first expression to execute when this method is called will be
new WebClient().DownloadDataTaskAsync(uri)
, which is another asynchronous method returning aTask<byte[]>
. Because this method is asynchronous, it will not download the entire batch of data before returning. Instead, it will begin the download process using a non-blocking mechanism (such as a background thread or idle scheduling), and immediately return an unresolved, unrejectedTask<byte[]>
to this function. - With the
await
keyword attached to theTask
, this function will immediately proceed to return aTask<int>
to its caller, who may then continue on with other processing as needed. - Once
DownloadDataTaskAsync()
finishes its download, it will resolve theTask
it returned with the downloaded data. This will trigger a callback and causeFindPageSize
to continue execution by assigning that value todata
. - Finally, the method returns
data.Length
, a simple integer indicating the length of the array. The compiler re-interprets this as resolving theTask
it returned earlier, triggering a callback in the method's caller to do something with that length value.
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 you to await multiple promises at once or in some special pattern (such as C#'s Task.WhenAll()
, 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 into more basic structures at compile time. For instance, the C# compiler would likely translate the above code to something like the following before translating it to IL:
public Task<int> FindPageSize(Uri uri)
{
Task<byte[]> data_task = new WebClient().DownloadDataTaskAsync(uri);
Task<int> after_data_task = data_task.ContinueWith((original_task) => {
return original_task.Result.Length;
});
return after_data_task;
}
Also, 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
keyword either and can instead return a promise object directly. For instance, a function might be able to function synchronously and thus provide a promise that immediately resolves to some result value (such as C#'s Task.FromResult()
), or it may be implemented in terms of some other promise-returning method that happens to return the exact promise needed (such as when deferring to an overload).
One important caveat of using asynchronous functionality is that, while waiting on the promise targeted by await
to complete, other events may occur in the meantime and potentially break assumptions that would remain true in a simpler, single-threaded program with blocking I/O. For instance, the following code, while always succeeding in a single-threaded model with await
, may instead trigger the assertion:
var a = state.a;
var data = await new WebClient().DownloadDataTaskAsync(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;
However, in most programs using the async/await structure idiomatically, this is not an issue, as the promise structure supports a robust message-passing architecture that eliminates the need to share global state at all - i.e. in the above code block, there would be no need to share the state
object between threads. [3]
In F#
An F# release of 2007 featured asynchronous workflows.[4]. In this initial version, await
was called let!
.
In C#
In C# versions before C# 7, async methods are required to return either void
, Task
, or Task<T>
. This has been expanded in C# 7 to include certain other types such as ValueTask<T>
. 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.[5]
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
.
public async Task<int> SumPageSizesAsync(IList<Uri> uris)
{
int total = 0;
foreach (var uri in uris) {
statusText.Text = string.Format("Found {0} bytes ...", total);
var data = await new WebClient().DownloadDataTaskAsync(uri);
total += data.Length;
}
statusText.Text = string.Format("Found {0} bytes total", total);
return total;
}
In Scala
In the experimental Scala-async extension to Scala, await
is a "method", although it does not operate like an ordinary method. Furthermore, unlike in C# 5.0 in which a method must be marked as async, in Scala-async, a block of code is surrounded by an async "call".
How it works
In Scala-async, async
is actually implemented using a Scala macro, which causes the compiler to emit different code, and produce a finite state machine implementation (which is considered to be more efficient than a monadic implementation, but less convenient to write by hand).
There are plans for Scala-async to support a variety of different implementations, including non-asynchronous ones.
In Python
Python 3.5 has added support for Async/Await as described in PEP0492 (https://www.python.org/dev/peps/pep-0492/).
In JavaScript
The await operator in JavaScript can only be used from inside an async function. 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.[6]
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.[7]
Here's an example (modified from this[8] article):
async function createNewDoc() {
let response = await db.post({}); // post a new doc
return await db.get(response.id); // find by id
}
async function main() {
try {
let doc = await createNewDoc();
console.log(doc);
} catch (err) {
console.log(err);
}
}()
Node.js version 8 includes a utility that enables using the standard library callback-based methods as promises.[9]
Async functions always return a promise. If the coder explicitly returns a value at the end of the async function, the promise will be resolved with that value; otherwise, it resolves with undefined
.[10] This means async functions can be chained like pure promise based functions.
In C++
In C++, await is a part of Coroutines TS, and is using co_await keyword. Coroutines TS may or may not be merged into C++20 [11], but all of MSVC, GCC, and Clang compilers are already supporting at least certain form of co-await.
See also
References
- ^ "Scala Async". Retrieved 20 October 2013.
- ^ "alexcrichton/futures-await". GitHub. Retrieved 2018-03-29.
- ^ 'No Bugs' Hare. Eight ways to handle non-blocking returns in message-passing programs CPPCON, 2018
- ^ https://blogs.msdn.microsoft.com/dsyme/2007/10/10/introducing-f-asynchronous-workflows/
- ^ "Async/Await - Best Practices in Asynchronous Programming". Retrieved 2 May 2017.
- ^ "await - JavaScript (MDN)". Retrieved 2 May 2017.
- ^ "jQuery Core 3.0 Upgrade Guide". Retrieved 2 May 2017.
- ^ "Taming the asynchronous beast with ES7". Retrieved 12 November 2015.
- ^ https://nodejs.org/en/blog/release/v8.0.0/#improved-support-for-promises
- ^ Chiang, George. "Introduction to JavaScript Async Functions- Promises simplified".
{{cite web}}
: Cite has empty unknown parameter:|dead-url=
(help) - ^ [1]