当前位置:网站首页>Async/await
Async/await
2022-06-23 17:50:46【DerekYuYi】
- proposal :SE-0296
- author :John McCall, Doug Grego
- Audit Supervisor :Ben Cohen
- state : stay Swift 5.5 The implemented
- Decision making records : The basic principle , Allow in
async
Load up
Introduce
At present Swift Used in development closures and completion handlers Handle a lot of asynchronous programming , But these API It's hard to use. . Especially when we need to call multiple asynchronous operations , Multiple error handling (error handling), Or you need to process the control flow when the asynchronous callback completes , In these cases, the code becomes hard to read . This proposal describes a language extension , Make the above problems more natural , It's not easy to make mistakes .
This design will Collaborative program model Introduced to the Swift. Function can optionally use async
, It allows programmers to combine complex asynchronous operations using conventional control flow mechanisms . The compiler will convert the asynchronous functions into a set of appropriate closure And state machine .
This proposal defines the semantics of asynchronous functions . in addition , Concurrent programming is not covered in this article . Concurrency is introduced separately in the structured concurrency proposal . Associate asynchronous functions with concurrent execution tasks in the structured concurrency proposal , And provide create 、 Query and cancel the task API.
Swift-evolution Key timeline : Key nodes 1, Key nodes 2
motivation :Completion handlers Non optimal scheme
Use explicit callbacks ( That is to say Completion handlers) There are many problems in asynchronous programming of , This is discussed below . In this article, we suggest introducing asynchronous functions into the language to solve these problems . These asynchronous functions allow asynchronous code to be written synchronously . They also allow the implementation to reason directly about the execution patterns of the code , This enables callbacks to run more efficiently .
Question 1 : Pyramid doom
A series of simple asynchronous operations usually require deeply nested closures . The following example can illustrate this point :
func processImageData1(completionBlock: (_ result: Image) -> Void) { loadWebResource("dataprofile.txt") { dataResource in loadWebResource("imagedata.dat") { imageResource in decodeImage(dataResource, imageResource) { imageTmp in dewarpAndCleanupImage(imageTmp) { imageResult in completionBlock(imageResult) } } } } } processImageData1 { image in display(image) }
This shallow set of code is commonly referred to as “ Pyramid doom ”, Code reading difficulties , It is also difficult to track the code running . besides , Using a bunch of closures can also have some other effects , This point is discussed below .
Question two :Error handling
Callbacks can complicate error handling .Swift 2.0 Introduced an error handling model for synchronous code , But interfaces based on asynchronous callbacks don't get any benefit :
// (2a) Using a `guard` statement for each callback: func processImageData2a(completionBlock: (_ result: Image?, _ error: Error?) -> Void) { loadWebResource("dataprofile.txt") { dataResource, error in guard let dataResource = dataResource else { completionBlock(nil, error) return } loadWebResource("imagedata.dat") { imageResource, error in guard let imageResource = imageResource else { completionBlock(nil, error) return } decodeImage(dataResource, imageResource) { imageTmp, error in guard let imageTmp = imageTmp else { completionBlock(nil, error) return } dewarpAndCleanupImage(imageTmp) { imageResult, error in guard let imageResult = imageResult else { completionBlock(nil, error) return } completionBlock(imageResult) } } } } } processImageData2a { image, error in guard let image = image else { display("No image today", error) return } display(image) }
hold [Result](https://github.com/apple/swift-evolution/blob/main/proposals/0235-add-result.md)
Added to the standard library to improve Swift APIs Error handling of . Asynchronous functions are contributory Result
One of the main reasons :
// (2b) Using a `do-catch` statement for each callback: func processImageData2b(completionBlock: (Result<Image, Error>) -> Void) { loadWebResource("dataprofile.txt") { dataResourceResult in do { let dataResource = try dataResourceResult.get() loadWebResource("imagedata.dat") { imageResourceResult in do { let imageResource = try imageResourceResult.get() decodeImage(dataResource, imageResource) { imageTmpResult in do { let imageTmp = try imageTmpResult.get() dewarpAndCleanupImage(imageTmp) { imageResult in completionBlock(imageResult) } } catch { completionBlock(.failure(error)) } } } catch { completionBlock(.failure(error)) } } } catch { completionBlock(.failure(error)) } } } processImageData2b { result in do { let image = try result.get() display(image) } catch { display("No image today", error) } }
// (2c) Using a `switch` statement for each callback: func processImageData2c(completionBlock: (Result<Image, Error>) -> Void) { loadWebResource("dataprofile.txt") { dataResourceResult in switch dataResourceResult { case .success(let dataResource): loadWebResource("imagedata.dat") { imageResourceResult in switch imageResourceResult { case .success(let imageResource): decodeImage(dataResource, imageResource) { imageTmpResult in switch imageTmpResult { case .success(let imageTmp): dewarpAndCleanupImage(imageTmp) { imageResult in completionBlock(imageResult) } case .failure(let error): completionBlock(.failure(error)) } } case .failure(let error): completionBlock(.failure(error)) } } case .failure(let error): completionBlock(.failure(error)) } } } processImageData2c { result in switch result { case .success(let image): display(image) case .failure(let error): display("No image today", error) } }
Obviously use Result
It is easier to deal with errors , But the problem of closure nesting still exists .
Question 3 : Difficult and error prone condition execution
Conditional execution of asynchronous functions has always been a pain point . such as , Suppose we want to rotate the image after we get it , But sometimes before the rotation operation , You must call an asynchronous function to decode the picture . The best way to build this function is in the intermediate helper closure ( Commonly referred to as continuation closure) Write the code of rotating pictures in , This closure is in completion handler In accordance with the conditions . For example, the pseudo code of this example can be written as follows :
func processImageData3(recipient: Person, completionBlock: (_ result: Image) -> Void) { let swizzle: (_ contents: Image) -> Void = { // ... continuation closure that calls completionBlock eventually } if recipient.hasProfilePicture { swizzle(recipient.profilePicture) } else { decodeImage { image in swizzle(image) } } }
This pattern is the opposite of the normal top-down organization function : Code executed in the second half of a function must appear before the first half of the function is executed . To refactor this function , You must think carefully about auxiliary closures (continuation closure) Capture in , Because closures are in completion handler Use in . As the number of asynchronous functions executed by conditions increases , The more complicated the problem becomes , At last, there is an inversion “ The pyramid of doom ” The phenomenon ( problem 1 Mentioned in “ Pyramid doom ”).
Question 4 : It's easy to make mistakes
It is easy for developers to forget to call the correct completion handler block Go straight back , Jump out of asynchronous operation prematurely . When we forget this operation , Programs sometimes become difficult to debug , There will also be some strange problems :
func processImageData4a(completionBlock: (_ result: Image?, _ error: Error?) -> Void) { loadWebResource("dataprofile.txt") { dataResource, error in guard let dataResource = dataResource else { return // <- forgot to call the block } loadWebResource("imagedata.dat") { imageResource, error in guard let imageResource = imageResource else { return // <- forgot to call the block } ... } } }
When you remember to call block when , You may still forget to call block After performing return operation :
func processImageData4b(recipient:Person, completionBlock: (_ result: Image?, _ error: Error?) -> Void) { if recipient.hasProfilePicture { if let image = recipient.profilePicture { completionBlock(image) // <- call block after , Forget to execute return operation } } }
In fact, I want to thank guard
grammar , To some extent, it prevents forgetting to execute when encoding return operation , But there is no guarantee that all .
Question five : because completion handlers It's hard to use. , A lot of API Use synchronization to complete the definition
This is really hard to measure , But the author thinks , Define and use asynchrony APIs ( Use completion handler) The embarrassment of has led to many APIs Is defined as an obvious synchronous behavior , Even if they can be blocked as asynchronously . This will be in UI It causes uncertain performance and response fluency problems . For example, loaders . And when asynchrony is critical to achieving scale , It will also result in the inability to use these api. For example, server side .
Proposed solution : async/await
An asynchronous function , Often called async/await, Allow asynchronous code to be written as linear and synchronous code . It does this by allowing programmers to take full advantage of the same language structures available in synchronous code , Solve most of the problems described above directly .async/await The use of also naturally preserves the semantic structure of the code , Provide at least 3 Information needed for cross domain language improvement :(1) Better performance of asynchronous code ;(2) Better instrumentality , In the debug , evaluating (profiling) Provides a consistent experience with studying code ;(3) Provides a basis for subsequent concurrency features such as task priority and cancel task . The example in the previous section demonstrates async/await How to greatly simplify asynchronous code :
func loadWebResource(_ path: String) async throws -> Resource func decodeImage(_ r1: Resource, _ r2: Resource) async throws -> Image func dewarpAndCleanupImage(_ i : Image) async throws -> Image func processImageData() async throws -> Image { let dataResource = try await loadWebResource("dataprofile.txt") let imageResource = try await loadWebResource("imagedata.dat") let imageTmp = try await decodeImage(dataResource, imageResource) let imageResult = try await dewarpAndCleanupImage(imageTmp) return imageResult }
Most of it is about async/await The description of is discussed through a general implementation mechanism : The compilation process of dividing a function into parts . To understand how a machine works , This is important at lower levels of language abstraction , But at a higher level of language abstraction , We encourage you to ignore this mechanism . contrary , Think of asynchronous functions as ordinary functions with the special ability to give up their threads . Asynchronous functions do not use this capability directly , But when they call , Some calls require them to abandon the thread they are in , Then wait for the execution result . When execution is complete , The function continues to execute from the point of waiting .
Asynchronous and synchronous functions look a lot like . Synchronous functions can be called , When a function call is initiated , The synchronization function waits for the call to complete . Once the call is complete , Control returns to the function and continues from where it stopped . The same is true for asynchronous functions : Asynchronous functions can make function calls , When a function call is initiated , Asynchronous functions usually wait directly for the call to complete . Once the call is complete , Control returns to the function and continues from where it stopped . The only difference is , Synchronous functions can take full advantage of their threads and their stack ( part ), Asynchronous functions can discard the stack completely , And use their own storage . This extra functionality for asynchronous functions has an implementation cost , But we can reduce this cost by designing it as a whole .
Because an asynchronous function must be able to abandon its thread , The synchronization function doesn't know how to abandon the thread , So in general , Synchronous functions cannot call asynchronous functions . If you do , An asynchronous function will give up some of the threads it brings , A synchronous function that calls an asynchronous function will treat it as a return and continue to execute from where it stopped , But there is no return value at this time . The most common method is to block the entire thread , Until the asynchronous function has been restored and completed . This is completely contrary to the purpose of asynchronous functions , And have a bad systematic impact .
contrary , Asynchronous functions can call both synchronous and asynchronous functions . When an asynchronous function calls a synchronous function , First of all, the asynchronous function will not abandon the thread . actually , Asynchronous functions never autonomously give up their thread , They are only in asynchronous functions to a hanging point (suspension point) Will give up the thread . Hanging points can occur inside functions , Or it happens inside another asynchronous function called by the current function , But either way case, Both the asynchronous function and its caller will abandon the thread at the same time .( actually , Asynchronous functions are compiled to be thread independent during asynchronous calls , therefore , Only the innermost function needs to do other extra work .)
When the control flow returns an asynchronous function , It will be exactly restored to its original position . This does not mean that it will run on exactly the same thread as before , because swift Languages are not guaranteed to run after suspension . In this design , Threads are almost more like an implementation mechanism , Not part of the concurrent interface . However , Many asynchronous functions are not just asynchronous : They and the specified actors( Used for isolation ) Link together , And is always considered actor Part of .Swift Will ensure that these functions actually return to where they are located actor To complete the function execution . therefore , A library that directly uses threads for state isolation ( for example , By creating your own threads and scheduling tasks sequentially on them ), These threading models should generally be built as Swift Medium actors, So that these basic languages can run normally .
Hang the starting point (Suspension points)
The hanging point is the point at which the thread must be abandoned during the execution of an asynchronous function . Hanging points are often associated with certain and grammatically explicit Events . From a functional point of view , They are never hidden or asynchronous where they occur ( At this point is synchronous behavior ). The prototype of the hanging point is to call an asynchronous function that is related to different execution contexts .
The hanging point is only associated with explicit operational behavior , This is crucial . in fact , This proposal requires that all calls that may be suspended be included in await
Expression . These calls are called potential hang points , Because they don't know if they will be suspended : It depends on the code that is not visible at the call ( such as , Callees may rely on asynchrony I/O) And dynamic conditions ( for example , asynchronous I/O Whether it is necessary to wait for completion ).
On a potential hook await
The requirements of Swift Precedent , I.e. requirement try
Expressions cover calls to functions that may throw errors . It is very important to mark potential hanging points , Because hanging interrupts atomicity (suspensions interrupt atomicity). such as , If the asynchronous function is running in the context of synchronous queue protection , If you reach a certain starting point this time , This means that other code can be interleaved in the same synchronization queue . A classic but somewhat trite example of atomicity is the modeling of banks : If a deposit is deposited into an account , But before processing matching withdrawals , The operation is paused , And it creates another window , In this window , These funds can be used in double . For many Swift For the programmer , A more similar example is UI Threads : The hanging point can be displayed to the user UI The point of , therefore , The building part UI Then the suspended program may present a flashing 、 Partially built UI( For example, in the process of requesting background services ,UI Show rotating chrysanthemums , While waiting for the background data to return and the rendering to complete , This is a starting point ).( Please note that , Hanging points are also explicitly called in code that uses explicit callbacks : The hang occurs between the return point of the external function and the start point of the callback .) All potential hanging points are required to be marked , Allows programmers to safely assume that locations without potential hanging points will run atomically , And it's easier to identify problematic non atomic patterns .
Because potential hanging points can only appear explicitly at points marked inside asynchronous functions , So a long calculation will still block the thread . This can happen when calling a synchronous function that is only used to do a lot of calculations , Or you encounter a particularly large computation loop in an asynchronous function . In the above two scenarios , When these calculations run , No thread can insert code , It is usually correct that there is no code interference , But this can also become an extensibility issue . An asynchronous program that requires a lot of computation should usually be run in a separate context . When this is not possible , The base library will have some tools to pause and allow other operations to be inserted and run .
Asynchronous functions should avoid calling functions that block threads , Especially if they can prevent it from waiting for work that is currently running . such as , Getting a mutex can block , Until the currently running thread releases the mutex . Sometimes this is acceptable , But it must be used with caution to avoid introducing deadlock problems or false extensibility problems . contrary , Waiting for a condition variable may block , Until some arbitrary other task is scheduled , And signal this variable ; This model is quite different from the recommended practice .
Design details
An asynchronous function
Function types can use async
Mark , Indicates that the function is asynchronous :
func collect(function: () async -> Int) { ... }
Function and initialization declaration function (init) You can also use async
Mark :
class Teacher { init(hiringFrom: College) async throws { ... } private func raiseHand() async -> Bool { ... } }
reason :
async
Following the function parameter list , Because it is part of the function type and its declaration . This onethrows
The example is the same .
Use async
The declared function or initialized reference type is async
Function type . If the reference is a static reference to an instance method , It's asynchronous “ Inside ” Function type , Consistent with the general rules for this type of reference .
Some special functions, such as deinit
And access accessors ( For example, attributes and subscripts getters and setters) Out of commission async
.
reason : Attributes and subscripts only have getter Methods can be declared as
async
. At the same timeasync
setter The attributes and subscripts of the method mean that the reference can be used asinout
Pass on , And drill down to the attributes of the attribute itself , It depends. setter In fact, it is a moment ( synchronous , Not thrown ) operation . Only read-only is allowedasync
Attributes and subscripts , prohibitasync
Properties are simpler .
If functions exist at the same time async
and throws
, When making a statement async
Keywords must be in throws
front . This rule applies to async
and rethrows
.
reason : This order restriction is arbitrary , But it does no harm , It eliminates potential format disputes .
If the of a class async
The initialization function does not call the initialization function of the parent class , When the initialization function of the parent class has parameters , Synchronous and is the specified initialization function (designated initializer), Then the initialization function of this class will implicitly call super.init()
.
reason : If the parent class initialization function is asynchronous , Calls to asynchronous initialization functions are a potential starting point , therefore , call ( requirement
await
) It must be visible where it is called .
Asynchronous function types
Asynchronous function types are different from synchronous function types . however , A synchronous function type can be implicitly converted to its corresponding asynchronous function type . This one non-throwing Function to its corresponding throwing Function implicit conversion is similar to , Can be combined with asynchronous function transformation . for example :
struct FunctionTypes { var syncNonThrowing: () -> Void var syncThrowing: () throws -> Void var asyncNonThrowing: () async -> Void var asyncThrowing: () async throws -> Void mutating func demonstrateConversions() { // Okay to add 'async' and/or 'throws' asyncNonThrowing = syncNonThrowing asyncThrowing = syncThrowing syncThrowing = syncNonThrowing asyncThrowing = asyncNonThrowing // Error to remove 'async' or 'throws' syncNonThrowing = asyncNonThrowing // error syncThrowing = asyncThrowing // error syncNonThrowing = syncThrowing // error asyncNonThrowing = syncThrowing // error } }
Await expression
For asynchronous function types ( Contains the async
Direct call to function ) The value call of introduces a potential hang point . Any Hang point must occur in an asynchronous context ( such as async
function ). and , It must appear in await In the expression .
Look at this example :
// func redirectURL(for url: URL) async -> URL { ... } // func dataTask(with: URL) async throws -> (Data, URLResponse) { ... } let newURL = await server.redirectURL(for: url) let (data, response) = try await session.dataTask(with: newURL)
In this case , Task suspension may occur in redirectURL(for:)
and dataTask(with:)
After call , Because they are asynchronous functions . therefore , Two call expressions must be included in await
In the expression , Each of them contains a potential starting point . One await
May contain multiple potential hanging points . for example , We can write this to use a await
To cover the 2 A starting point :
let (data, response) = try await session.dataTask(with: server.redirectURL(for: url))
await
No other semantics , Follow try
It's like . It simply marks that an asynchronous call is in progress .await
The type of an expression is the type of its operand , The result is the result of the operand .await
There may be no potential hanging points , In this case, the compiler will give a warning , Follow try
The expression rules are the same :
let x = await synchronous() // warning: no calls to 'async' functions occur within 'await' expression
reason : Inside the function , It is important that asynchronous function calls be clearly identified , Because asynchronous functions may introduce hanging points , Breaking the atomicity of operations . The hang point may be inherent in the call ( Because asynchronous calls must be executed on different executors ) Or it may just be part of the callee's implementation . But in either case , It is semantically important , Programmers need to acknowledge this .
await
Expressions are also a representation of asynchronous code , Asynchronous code interacts with reasoning in closures . This can be seen from Closures Section for more information .
No async
Functional autoClosure, There must be no hanging point .
defer
block There must be no potential hanging points in the .
If both exist in the subexpression await
and try
Variants (try!
and try?
),await
Must be with the try
/try!
/try?
after :
let (data, response) = await try session.dataTask(with: server.redirectURL(for: url)) // error: must be `try await` let (data, response) = await (try session.dataTask(with: server.redirectURL(for: url))) // okay due to parentheses
reason : This restriction is arbitrary , But to prevent style disputes , It follows the same arbitrary
async throws
Sequence constraints .
Closures
closure You can have async
Function type . In this way closure have access to async
Mark :
{ () async -> Int in print("here") return await getInt() }
anonymous closure If you include await
expression , It will be inferred to have async
Function type .
let closure = { await getInt() } let closure2 = { () -> Int in print("here") return await getInt() }
Please note that , For closures async
The inference will not be passed to the closed function in the closure , Nested functions or closures , Because these contents are separable, asynchronous or synchronous . for example , In the following example , Only closure6
Will be inferred as async
:
// func getInt() async -> Int { ... } let closure5 = { () -> Int in // not 'async' let closure6 = { () -> Int in // implicitly async if randomBool() { print("there") return await getInt() } else { let closure7 = { () -> Int in 7 } // not 'async' return 0 } } print("here") return 5 }
Overload and overload resolution
The existing Swift API Usually by callback Interface to support asynchronous functions , Such as :
func doSomething(completionHandler: ((String) -> Void)? = nil) { ... }
Many like this API You can do it by adding async
To update :
func doSomething() async ->String { ... }
These two functions have different names and signatures , Although they both share the same basic function name . however , Both of them can make parameterless calls ( above completion handler Default values are provided ), This can cause problems for existing code :
doSomething() // problem: can call either, unmodified Swift rules prefer the `async` version
A similar problem also exists with functions that provide both synchronous and asynchronous versions , With the same signature api in . In this case... Is allowed API Providing asynchronous functions is more suitable for Swift Asynchronous scenario , It doesn't break backward compatibility . The new asynchronous function can also support cancel operations ( stay Structured Concurrency You can see the definition of asynchronous function cancellation in ).
// Existing synchronous API func doSomethingElse() { ... } // New and enhanced asynchronous API func doSomethingElse() async { ... }
In the first scenario ,Swift The overload rule of will call the function with default parameters first , So add the async
Function will destroy the original call doSomething(completionHandler:)
Existing code for , This results in the following errors :
error: `async` function cannot be called from non-asynchronous context
This brings problems to code evolution , Because developers of existing asynchronous libraries either have a mandatory compatibility interrupt ( such as , To a new master version ), Or all the new async
Versions need to have different names . The latter may produce a scheme , such as C#'s pervasive Async suffix.
In the second scenario , Both functions have the same signature and only async
Different keywords , This kind of situation will be generally recognized by the existing Swift Overload rule rejected . It is really not allowed to distinguish two functions by their effect words , For example, you can't define two only throws
Different functions :
// error: redeclaration of function `doSomethingElse()`.
This also creates problems for code evolution , Because developers of existing libraries cannot keep their existing synchronization API, To support new asynchronous features .
contrary , We propose an overload resolution rule to give the context of the call to select the appropriate function . For a given call , Overload resolution will preferentially select non in the synchronization context async
function ( Because such a context cannot contain calls to asynchronous functions ). and , Overload resolution takes precedence over... In the asynchronous context async
function ( Because this context should avoid jumping out of the asynchronous model into blocking API). When overload resolution selects async
Function time , The given call is still subject to “ Asynchronous function calls must occur at await
In the expression ” The limitation of .
Overload resolution rules depend on the synchronous or asynchronous context , In the corresponding environment , The compiler selects only one function overload . Selecting an asynchronous function overload requires await
expression , Introduction as a starting point for all potential applications :
func f() async { // In an asynchronous context, the async overload is preferred: await doSomething() // Compiler error: Expression is 'async' but is not marked with 'await' doSomething() }
In Africa async
Functions and without any await
In the closure of an expression , Compiler selects non async
heavy load :
func f() async { let f2 = { // In a synchronous context, the non-async overload is preferred: doSomething() } f2() }
Autoclosures
Functions may not take async
Function type autoClosure Parameters , Unless the function itself is asynchronous . for example , The following statement is not standardized :
// error: async autoclosure in a function that is not itself 'async' func computeArgumentLater<T>(_ fn: @escaping @autoclosure () async -> T) { }
There are several reasons for this limitation , Here's an example :
// func getIntSlowly() async -> Int { ... } let closure = { computeArgumentLater(await getIntSlowly()) print("hello") }
At first glance ,await
The expression seems to imply that the programmer , Calling computeArgumentLater(_:)
There was a potential starting point , But this is not the actual scenario : Potential hanging points are passed in and used in computeArgumentLater(_:)
intra-function autoclosuse in . This also directly brings about some problems . First ,await
The fact that occurs before the call means that closure
Will be inferred to contain async
Function type , This is not true : All in closure
The code in is synchronized . secondly , because await
The operation only needs to include a potential hanging point within it , So an equivalent rewrite of the call should look like this :
await computeArgumentLater(getIntSlowly())
however , Because the parameter is autoclosure, This rewrite no longer preserves the original semantics . therefore , By ensuring that async
autoclosure Parameters can only be used in an asynchronous context , Yes async
autoclosure Parameter constraints avoid these problems .
Agreement consistency
The agreement may also be declared as async
. Such an agreement can be reached through async
Or the synchronization function . However , Synchronization function requirements cannot be synchronized async
Function implementation . for example :
protocol Asynchronous { func f() async } protocol Synchronous { func g() } struct S1: Asynchronous { func f() async { } // okay, exactly matches } struct S2: Asynchronous { func f() { } // okay, synchronous function satisfying async requirement } struct S3: Synchronous { func g() { } // okay, exactly matches } struct S4: Synchronous { func g() async { } // error: cannot satisfy synchronous requirement with an async function }
This behavior follows the subtypes of asynchronous functions / Implicit conversion rules , just as throws
The same rules of behavior .
Source code compatibility
This proposal is an addition : The existing code doesn't use any new features ( For example, no async
Functions and Closures ) And will not be affected . however , Brought in 2 New context keywords ,async
and await
.
async
In grammar ( Function declaration and function type ) The location allows us to put... Without breaking source code compatibility async
Treat as context keywords . In well formed code , User defined async
Cannot appear in these grammatical positions .
await
Contextual keywords are more likely to cause confusion , Because it appears inside the expression . for example , We can do it in Swift It's called await
Function of :
func await(_ x: Int, _ y: Int) -> Int { x + y } let result = await(1, 2)
At present Swift In language , Yes await
A function call is a well - structured piece of code . But with the emergence of this proposal , This code becomes a subexpression (1, 2)
Of await
expression . This code will be shown as a compilation error in the existing program , because await
Can only be used in an asynchronous context , It doesn't exist in a context like this . These functions do not seem to be common , So we believe that as an introduction async/await Part of , This is an acceptable source code corruption .
Yes ABI The effect of stability
Asynchronous functions and function types can be added to ABI in , No effect ABI The stability of , Because existing functions ( synchronous ) And function types are not affected .
Yes API The impact of scalability
async
Functional ABI With the synchronization function ABI Completely different ( for example , They have completely incompatible invocation rules ), So add or remove from a function or type async
, No impact on scalability .
The future direction
reasync
Swift in rethrows
It's the same mechanism , Used to indicate that a particular function only passes itself one parameter throw Function of the throw operation . for example Sequence.map
utilize rethrows
Because this operation can only throw The way is transform Whether the method itself can throws:
extension Sequence { func map<Transformed>(transform: (Element) throws -> Transformed) rethrows -> [Transformed] { var result = [Transformed]() var iterator = self.makeIterator() while let element = iterator.next() { result.append(try transform(element)) // note: this is the only `try`! } return result } }
The actual use map
:
_ = [1, 2, 3].map { String($0) } // okay: map does not throw because the closure does not throw _ = try ["1", "2", "3"].map { (string: String) -> Int in guard let result = Int(string) else { throw IntParseError(string) } return result } // okay: map can throw because the closure can throw
This concept can also be applied to async
On the function . for example , We can imagine , When map
Parameter with reasync
when ,map
Functions also become asynchronous functions :
extension Sequence { func map<Transformed>(transform: (Element) async throws -> Transformed) reasync rethrows -> [Transformed] { var result = [Transformed]() var iterator = self.makeIterator() while let element = iterator.next() { result.append(try await transform(element)) // note: this is the only `try` and only `await`! } return result } }
Conceptually , This is good : When map
The entry parameter of is async
Function time ,map
Would be considered to be async
( Results need to be used await
), So if the input parameter is not async
function ,map
Will be considered synchronous ( Will not use await
To accept the results ).
actually , There are several problems :
- This may not be sequential asynchrony
map
A very good implementation . It's more likely that , We want concurrent implementations ( For example ) The maximum number of core elements processed concurrently . - throw Functional ABI Designed to make
rethrows
It is possible for functions to act as non - throw function , therefore , One ABI The entrance is enough to handle throw Calls and non-throw Function call . This is not the case with asynchronous functions , Asynchronous functions have completely different ABI, Its efficiency must be lower than that of the synchronization function ABI.
image Sequence.map
These may become concurrent functions ,reasync
More like a wrong tool : Yes async
Closure overloading provides separate ( concurrent ) Implementation looks better . therefore , And rethrows
comparison ,reasync
May not be applicable .
without doubt ,reasync
There are some uses . For example, for optional ??
The operator ,async
The implementation is now very close to the synchronization implementation :
func ??<T>( _ optValue: T?, _ defaultValue: @autoclosure () async throws -> T ) reasync rethrows -> T { if let value = optValue { return value } return try await defaultValue() }
In this case , The above can be solved through two entry points ABI problem : One is when the parameter is async
, The other parameter is not async
. However, due to the complexity of the implementation , The author has not yet submitted this design proposal .
alternative
stay await
Implemented on try
Many asynchronous API Involving documents I/O, The Internet , Or other operations that may fail , So these operations should also be async
and throws
. This also means that in the place of the call ,try await
Will be called repeatedly many times . To reduce this repeated template invocation ,await
Can achieve try
, Then the effect of the following two lines of code should be the same :
let dataResource = await loadWebResource("dataprofile.txt") let dataResource = try await loadWebResource("dataprofile.txt")
In the end, there was no await
Realization try
Because they express different considerations : await
Represents a potential hanging point , Other code may execute between your call and its return , and try
It's about block External control flow .
The other one wants to await
Implemented on try
Your motivation is related to task cancellation . If you cancel building a task to throw an error , And each potential hold point implicitly checks whether the task has been canceled , Then each potential hook can do a throw operation : such case Next await
Can achieve try
Because of every await
Can exit with errors .Structured Concurrency The proposal includes a description of the task cancellation , It does not model the cancellation task as throwing an error , Nor does it introduce an implicit cancellation check at every potential hang point .
start-up async Mission
Because only async
Code can call other async
Code , This proposal does not provide a way to initialize asynchronous code . This was done intentionally : All asynchronous code runs on "task" In the context of ."task" yes Structured Concurrency Concepts defined in the proposal . This proposal provides for the adoption of @main
The ability to define asynchronous entry points for a program , such as :
@main struct MyProgram { static func main() async { ... } }
in addition , In this proposal , top floor (top-level) Code cannot be considered context , So the following program format is incorrect :
func f() async -> String { "hello, asynchronously" } print(await f()) // error: cannot call asynchronous function in top-level code
This will also be addressed in subsequent proposals , This proposal will appropriately consider top-level variables (top-level variables).
Considerations for top-level code do not affect the..., as defined in this proposal async/await The basic mechanism .
hold await As a grammar sugar
The proposal puts async
Function as Swift The core of the type system , Distinguish synchronization functions . Another design is to keep the type system unchanged , But in some Future<T, Error>
Use... In type async
and await
grammar . for example :
async func processImageData() throws -> Future<Image, Error> { let dataResource = try loadWebResource("dataprofile.txt").await() let imageResource = try loadWebResource("imagedata.dat").await() let imageTmp = try decodeImage(dataResource, imageResource).await() let imageResult = try dewarpAndCleanupImage(imageTmp).await() return imageResult }
This design compares to the proposal in this article , There are many shortcomings :
- stay Swift In ecosystem , There is no universal
Future
Types can be built . If Swift The ecosystem has basically identified a single future type ( for example , There is already one in the standard library ), A way similar to the above syntax sugar will appear in the existing code . If there is no such type , People will have to try to abstract all the different types of future types with some future protocol . This may be possible for some types in the future , However, any guarantee of the behavior or performance of asynchronous code is waived . - And
throws
The design of is inconsistent . In this model , The result type of an asynchronous function is future type ( Or something elseFuturable
type ), Instead of the actual return value . They must always beawait
ed( So it's suffix syntax ), otherwise , When you really care about the results of asynchronous operations , You will use futures. When many other aspects of asynchronous design are deliberately avoided for future When thinking , This becomes a system with future Model programming , Instead of an asynchronous programming model . - take
async
Deleting from the type system will eliminate theasync
The ability to overload . Look at the previous section , understandasync
The reason for the overload on the . - Future Is a relatively heavy type , And generating one for each asynchronous operation costs a lot in code size and performance . contrary , High integration with system types allows
async
The function is designed to build and optimize asynchronous functions , For efficient suspend operations .Swift All levels of the compiler and runtime can future The return function cannot be optimized in a wayasync
function .
Version history
- Review changes :
- Use
try await
Instead ofawait try
. - Add syntax sugar alternative design .
- The modification proposal allows stay
async
Load up .
- Use
- Other changes :
- You can no longer directly overload asynchronous and non asynchronous functions , However , There are other reasons to support overloaded solutions .
- Add implicit conversion from synchronous function to asynchronous function .
- increase
await try
Order constraints to matchasync throws
Limit . - increase
async
Initialization support . - Added to meet
async
Support for synchronization functions required by the protocol . - increase
reasync
The discussion of the . - increase
await
It doesn't containtry
The reason of . - increase
async
Reason to follow in function argument list .
- first draft ( file and Community discussion node )
Other relevant proposals
In addition to this proposal , There are a number of related proposals that include Swift Other aspects of the concurrency model :
- And Objective-C Concurrent interoperation of : Description and Objective-C Interaction , Especially in accepting completion handler The asynchronous Objective-C Methods and
@objc async
Swift The relationship between methods . - Structured concurrency : Describe the task structure used in asynchronous invocation , Creation of subtasks and detached tasks 、 Cancel 、 Prioritization and other task management API.
- Actors: Describe the participant model , Provides state isolation for concurrent programming .
边栏推荐
- Discussion on five kinds of zero crossing detection circuit
- MySQL transaction submission process
- Answer 01: why can Smith circle "allow left string and right parallel"?
- What is the mobile account opening process? Is it safe to open an account online now?
- How to open an account through online stock? Is online account opening safe?
- [mae]masked autoencoders mask self encoder
- JSON - learning notes (message converter, etc.)
- Thymeleaf - learning notes
- 12 initialization of beautifulsoup class
- Analysis of object class structure in Nanny level teaching (common class) [source code attached]
猜你喜欢
Add new members to the connector family! Scenario connector helps enterprises comprehensively improve the operational efficiency of business systems
Tencent Qianfan scene connector: worry and effort saving automatic SMS sending
Redis ubuntu18.04.6 intranet deployment
Interpretation of eventbus source code
Year end: the "time value" and business methodology of live broadcast E-commerce
JS custom error
JS reset form
JS regular verification time test() method
History of storage technology: from tape to hardware liquefaction
First use of kubernetes cronjob
随机推荐
- Cross browser common events
- Tencent three sides: how to duplicate 4billion QQ numbers?
- Goframe framework: fast implementation of service end flow limiting Middleware
- What is the problem with TS File Error 404 when easynvr plays HLS protocol?
- [JS reverse hundred examples] pedata encryption information and zlib Application of gunzipsync()
- How code 39 check bits are calculated
- Listen attentively and give back sincerely! Pay tribute to the best product people!
- How to quickly obtain and analyze the housing price in your city?
- Petitpotam – NTLM relay to ad CS
- Explanation of the principle and code implementation analysis of rainbow docking istio
- Drawing black technology - easy to build a "real twin" 2D scene
- How to make a shirt certificate
- High availability solution practice of mongodb advanced applications (4)
- Introduction to GTS Academy
- Digital intelligent supply chain collaboration solution for new energy industry
- How to create a three elimination game
- The mail function is normal locally, and the ECS reports an error
- POC about secureworks' recent azure Active Directory password brute force vulnerability
- Similarities and differences between Chinese and American electronic signature SaaS
- Company offensive operation guide
- Go unit test
- Intelligent supply chain collaborative management solution for logistics industry
- PostgreSQL series articles -- the world's most advanced open source relational database
- Script to view the execution of SQLSERVER database stored procedures
- Transaction processing of cloud development database
- Skills that all applet developers should know: applying applet components
- ACM players take you to play with the array!
- Kotlin invoke convention makes kotlin code more concise
- January 5, 2022: there are four kinds of rhythms: AABB, ABAB and ABB
- Lighthouse open source application practice: o2oa
- . Net cloud native architect training camp (responsibility chain mode) -- learning notes
- Android kotlin exception handling
- Best practices cloud development cloudbase content audit capability
- The principle of MySQL index algorithm and the use of common indexes
- Goframe framework: graceful closing process
- Nanny level teaching! Take you to play with time complexity and space complexity!
- How to design a seckill system - geek course notes
- Method of copying web page content and automatically adding copyright information (compatible with ie, Firefox and chrome)
- [Hyperf]Entry “xxxInterface“ cannot be resolved: the class is not instantiable
- How to use JSON data format