Jer*_*ere 20 async-await swift xcode14
我在 Swift 5.5 中遇到了问题,但我不太明白解决方案。
import Foundation
func testAsync() async {
var animal = "Dog"
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
animal = "Cat"
print(animal)
}
print(animal)
}
Task {
await testAsync()
}
Run Code Online (Sandbox Code Playgroud)
这段代码会导致错误
Mutation of captured var 'animal' in concurrently-executing code
Run Code Online (Sandbox Code Playgroud)
但是,如果将animal变量移离此异步函数的上下文,
import Foundation
var animal = "Dog"
func testAsync() async {
DispatchQueue.main.asyncAfter(deadline: .now() + 2) {
animal = "Cat"
print(animal)
}
print(animal)
}
Task {
await testAsync()
}
Run Code Online (Sandbox Code Playgroud)
它会编译。我知道这个错误是为了防止数据竞争,但为什么移动变量会使其安全?
Rob*_*Rob 16
关于全局变量示例的行为,我可能会建议您参考Rob Napier\xe2\x80\x99s与全局变量可发送性相关的错误/限制的评论:
\n\n\n编译器在如何推理全局变量方面有很多限制。简短的答案是\xe2\x80\x9cdon\不创建全局可变变量。\xe2\x80\x9d它\xe2\x80\x98s出现在论坛上,但\xe2\x80\x98没有得到任何讨论。https://forums.swift.org/t/sendability-checking-for-global-variables/56515
\n
FWIW,如果您将其放入实际的应用程序中并将 \xe2\x80\x9cStrict Concurrency Checking\xe2\x80\x9d 构建设置更改为 \xe2\x80\x9cComplete\xe2\x80\x9d,您确实会在全局示例:
\n\n\n对 var \'animal\' 的引用不是并发安全的,因为它涉及共享可变状态
\n
这种对线程安全问题的编译时检测正在不断发展,Swift 6 中承诺出现许多新错误(这就是为什么他们\xe2\x80\x99 给了我们这个新的 \xe2\x80\x9c 严格并发检查\xe2\x80\x9d设置,以便我们可以开始使用不同级别的检查来检查我们的代码)。
\n无论如何,您可以使用参与者来提供与此值的线程安全交互:
\nactor AnimalActor {\n var animal = "Dog"\n \n func setAnimal(newAnimal: String) {\n animal = newAnimal\n }\n}\n\nfunc testAsync() async {\n let animalActor = AnimalActor()\n \n Task {\n try await Task.sleep(nanoseconds: 2 * NSEC_PER_SEC)\n await animalActor.setAnimal(newAnimal: "Cat")\n print(await animalActor.animal)\n }\n\n print(await animalActor.animal)\n}\n\nTask {\n await testAsync()\n}\nRun Code Online (Sandbox Code Playgroud)\n有关更多信息,请参阅 WWDC 2021\xe2\x80\x99s使用 Swift Actor 保护可变状态和 2022\xe2\x80\x99s使用 Swift Concurrency 消除数据竞争。
\n请注意,在上面,我避免使用 GCD API。这asyncAfter是古老的 GCD 技术,用于推迟某些工作而不阻塞当前线程。但新的Task.sleep(与旧的不同Thread.sleep)在并发系统中实现了相同的行为(并提供取消功能)。如果可能,我们应该避免在 Swift 并发代码库中使用 GCD API。
首先,如果可以的话,请使用结构化并发,正如其他答案所建议的那样。
我遇到了一个没有干净的结构化并发 API 的情况:一个需要非异步返回值的协议。
protocol Proto {
func notAsync() -> Value
}
Run Code Online (Sandbox Code Playgroud)
要计算值,需要异步方法调用。我选择了这个解决方案:
func someAsyncFunc() async -> Value {
...
}
class Impl: Proto {
func notAsync() -> Value {
return UnsafeTask {
await someAsyncFunc()
}.get()
}
}
class UnsafeTask<T> {
let semaphore = DispatchSemaphore(value: 0)
private var result: T?
init(block: @escaping () async -> T) {
Task {
result = await block()
semaphore.signal()
}
}
func get() -> T {
if let result = result { return result }
semaphore.wait()
return result!
}
}
Run Code Online (Sandbox Code Playgroud)
如果遇到相同的情况,您可以复制 UnsafeTask 类并在代码中使用它。
我认为这是一个相当丑陋的解决方案,例如:类型需要是一个类,因为结构需要进行并发检查,这意味着编译器在并发访问 和 时会semaphore出错result。据我所知,信号量应该是线程安全的,并且结果仅从一个上下文写入并由其余上下文读取。如果 T 是指针大小或更小,则写入是原子的,因此是“安全的”。在其他情况下,它可能不安全。尽管我可能会忽略一些并发边缘情况。开放征求建议。
| 归档时间: |
|
| 查看次数: |
13625 次 |
| 最近记录: |