将本地可变结构传递给异步 let 绑定
Passing a local mutable struct into an async let binding
我想创建一个 URL 请求并将其传递到异步 let 绑定中,这对我来说很自然:
func test() async {
// Force unwraps (!) are just for demo
var request = URLRequest(url: URL(string:"https://whosebug.com")!)
request.httpMethod = "GET" // just for example
// some more tinkering with `request` here.
// Error on this line: "Reference to captured var 'request' in concurrently-executing code"
async let responseData = URLSession.shared.data(for: request).0
// It works like this:
// let immutableRequest = request
// async let responseData = URLSession.shared.data(for: immutableRequest).0
// other stuff
print("Response body: \(String(data: try! await responseData, encoding: .utf8))")
}
为什么会出现错误? URLRequest
是一个结构,所以当我们将它传递给一个函数时,该函数应该得到该结构的副本,所以如果我在异步调用后修改 request
,它应该不会影响调用。
我知道调用是异步发生的,但我希望它在调用时捕获参数,然后继续执行,就像调用已经完成一样(因此,request
的副本在调用点已传递到 data(for: request)
.
此外,有没有一种方便的方法可以在不创建另一个 let
变量并且不使用闭包来初始化 request
的情况下执行此操作,例如:
let request: URLRequest = {
var result = URLRequest(url: URL(string:"https://whosebug.com")!)
result.httpMethod = "GET"
return result
}()
正如SE-0317 - async let bindings所说:
... async let
is similar to a let
, in that it defines a local constant that is initialized by the expression on the right-hand side of the =
. However, it differs in that the initializer expression is evaluated in a separate, concurrently-executing child task.
The child task begins running as soon as the async let
is encountered.
...
A async let
creates a child-task, which inherits its parent task's priority as well as task-local values. Semantically, this is equivalent to creating a one-off TaskGroup
which spawns a single task and returns its result ...
Similarly to the [group.addTask
] function, the closure is @Sendable
and nonisolated
, meaning that it cannot access non-sendable state of the enclosing context. For example, it will result in a compile-time error, preventing a potential race condition, for a async let
initializer to attempt mutating a closed-over variable:
var localText: [String] = ...
async let w = localText.removeLast() // error: mutation of captured var 'localText' in concurrently-executing code
The async let
initializer may refer to any sendable state, same as any non-isolated sendable closure.
所以,并不是复制data(for:delegate:)
的参数然后创建异步任务,而是反过来
通常,如果您使用闭包,您只需将 request
添加到闭包的捕获列表中,但在这种情况下这是不可能的。例如,您可以使用捕获列表自己创建一个 Task
,实现类似于 async let
的效果,但具有更大的控制权:
func test() async throws {
var request = URLRequest(url: URL(string:"https://httpbin.org/get")!)
request.httpMethod = "GET" // just for example
let task = Task { [request] in
try await URLSession.shared.data(for: request).0
}
// do some more stuff in parallel
print("Response body: \(String(data: try await task.value, encoding: .utf8) ?? "Not string")")
}
显然,您可以简单地 await
data(for:delegate:)
,而不是 async let
,问题就消失了:
func test() async throws {
var request = URLRequest(url: URL(string:"https://httpbin.org/get")!)
request.httpMethod = "GET" // just for example
let data = try await URLSession.shared.data(for: request).0
print("Response body: \(String(data: data, encoding: .utf8) ?? "Not string")")
}
我想创建一个 URL 请求并将其传递到异步 let 绑定中,这对我来说很自然:
func test() async {
// Force unwraps (!) are just for demo
var request = URLRequest(url: URL(string:"https://whosebug.com")!)
request.httpMethod = "GET" // just for example
// some more tinkering with `request` here.
// Error on this line: "Reference to captured var 'request' in concurrently-executing code"
async let responseData = URLSession.shared.data(for: request).0
// It works like this:
// let immutableRequest = request
// async let responseData = URLSession.shared.data(for: immutableRequest).0
// other stuff
print("Response body: \(String(data: try! await responseData, encoding: .utf8))")
}
为什么会出现错误? URLRequest
是一个结构,所以当我们将它传递给一个函数时,该函数应该得到该结构的副本,所以如果我在异步调用后修改 request
,它应该不会影响调用。
我知道调用是异步发生的,但我希望它在调用时捕获参数,然后继续执行,就像调用已经完成一样(因此,request
的副本在调用点已传递到 data(for: request)
.
此外,有没有一种方便的方法可以在不创建另一个 let
变量并且不使用闭包来初始化 request
的情况下执行此操作,例如:
let request: URLRequest = {
var result = URLRequest(url: URL(string:"https://whosebug.com")!)
result.httpMethod = "GET"
return result
}()
正如SE-0317 - async let bindings所说:
...
async let
is similar to alet
, in that it defines a local constant that is initialized by the expression on the right-hand side of the=
. However, it differs in that the initializer expression is evaluated in a separate, concurrently-executing child task.The child task begins running as soon as the
async let
is encountered....
A
async let
creates a child-task, which inherits its parent task's priority as well as task-local values. Semantically, this is equivalent to creating a one-offTaskGroup
which spawns a single task and returns its result ...Similarly to the [
group.addTask
] function, the closure is@Sendable
andnonisolated
, meaning that it cannot access non-sendable state of the enclosing context. For example, it will result in a compile-time error, preventing a potential race condition, for aasync let
initializer to attempt mutating a closed-over variable:var localText: [String] = ... async let w = localText.removeLast() // error: mutation of captured var 'localText' in concurrently-executing code
The
async let
initializer may refer to any sendable state, same as any non-isolated sendable closure.
所以,并不是复制data(for:delegate:)
的参数然后创建异步任务,而是反过来
通常,如果您使用闭包,您只需将 request
添加到闭包的捕获列表中,但在这种情况下这是不可能的。例如,您可以使用捕获列表自己创建一个 Task
,实现类似于 async let
的效果,但具有更大的控制权:
func test() async throws {
var request = URLRequest(url: URL(string:"https://httpbin.org/get")!)
request.httpMethod = "GET" // just for example
let task = Task { [request] in
try await URLSession.shared.data(for: request).0
}
// do some more stuff in parallel
print("Response body: \(String(data: try await task.value, encoding: .utf8) ?? "Not string")")
}
显然,您可以简单地 await
data(for:delegate:)
,而不是 async let
,问题就消失了:
func test() async throws {
var request = URLRequest(url: URL(string:"https://httpbin.org/get")!)
request.httpMethod = "GET" // just for example
let data = try await URLSession.shared.data(for: request).0
print("Response body: \(String(data: data, encoding: .utf8) ?? "Not string")")
}