2

我想创建一个 URL 请求并将其传递给 async let 绑定,这对我来说似乎很自然:

func test() async {
    // Force unwraps (!) are just for demo
    var request = URLRequest(url: URL(string:"https://stackoverflow.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://stackoverflow.com")!)
    result.httpMethod = "GET"
    return result
}()
4

1 回答 1

1

正如SE-0317 - async let bindings所说:

...async let与 a 相似let,因为它定义了一个由 . 右侧的表达式初始化的局部常量=。但是,它的不同之处在于初始化表达式是在一个单独的、并发执行的子任务中计算的。

async let子任务一遇到就开始运行。

...

Aasync let创建一个子任务,它继承其父任务的优先级以及任务本地值。从语义上讲,这相当于创建一个一次性的,TaskGroup它产生一个任务并返回其结果......

group.addTask与 [ ] 函数类似,闭包是@Sendableand nonisolated,这意味着它不能访问封闭上下文的不可发送状态。例如,它会导致编译时错误,防止潜在的竞争条件,async let初始化器尝试改变封闭变量:

var localText: [String] = ...
async let w = localText.removeLast() // error: mutation of captured var 'localText' in concurrently-executing code

初始化器async let可以引用任何可发送状态,与任何非隔离的可发送闭包相同。

所以,不是先data(for:delegate:)复制参数to然后创建异步任务,而是反过来。

通常,如果您使用闭包,您只需添加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")")
}
于 2022-01-24T21:53:56.200 回答