我遇到了一個問題,我的異步函式導致 UI 凍結,即使我在任務中呼叫它們,并嘗試了許多其他方法(GCD、Task.detached 以及兩者的組合)。在對其進行測驗后,我找出了導致這種行為的部分,我認為這是 Swift/SwiftUI 中的一個錯誤。
錯誤描述
我想每隔 x 秒在后臺計算一次,然后通過更新 @Binding/@EnvironmentObject 值來更新視圖。為此,我使用了一個計時器,并通過在 .onReceive 修飾符中訂閱它來監聽它的變化。這個修改器的操作只是一個包含異步函式的任務(await foo())。這就像預期的那樣作業,所以即使 foo 函式暫停幾秒鐘,UI 也不會凍結,但是如果我將一個 @EnvironmentObject 添加到視圖中,UI 將在 foo 函式的持續時間內無回應。
視圖中沒有EnvironmentVariable的行為的 GIF :

視圖中具有 EnvironmentVariable 的行為的 GIF:

最小的,可重現的例子
這只是一個按鈕和一個滾動視圖來查看影片。當您按下代碼中存在 EnvironmentObject 的按鈕時,UI 會凍結并停止對手勢的回應,但只需洗掉那一行,UI 就可以正常作業,保持回應和更改屬性。
import SwiftUI
class Config : ObservableObject{
@Published var color : Color = .blue
}
struct ContentView: View {
//Just by removing this, the UI freeze stops
@EnvironmentObject var config : Config
@State var c1 : Color = .blue
var body: some View {
ScrollView(showsIndicators: false) {
VStack {
HStack {
Button {
Task {
c1 = .red
await asyncWait()
c1 = .green
}
} label: {
Text("Task, async")
}
.foregroundColor(c1)
}
ForEach(0..<20) {x in
HStack {
Text("Placeholder \(x)")
Spacer()
}
.padding()
.border(.blue)
}
}
.padding()
}
}
func asyncWait() async{
let continueTime: Date = Calendar.current.date(byAdding: .second, value: 2, to: Date())!
while (Date() < continueTime) {}
}
}
struct ContentView_Previews: PreviewProvider {
static var previews: some View {
ContentView()
}
}
免責宣告
我對使用該專案所需的并發級別相當陌生,所以我可能會遺漏一些東西,但我找不到與搜索詞“Task”和“EnvironmentObject”相關的任何內容。
問題
這真的是一個錯誤嗎?還是我錯過了什么?
uj5u.com熱心網友回復:
據我所知,你的代碼,不管有沒有,都@EnvrionmentObject應該阻塞主執行緒。它并非沒有的事實可能是一個錯誤,但反之則不然。@EnvironmentObject
在您的示例中,您阻塞了主執行緒——您呼叫了一個async在從其父級繼承的背景關系上運行的函式。它的父級是 a View,并在主要參與者上運行。
通常在這種情況下,對于在繼承背景關系之外實際運行的東西存在混淆。您提到 using Task.detached,但只要您的功能仍標記async在父級上,沒有其他修改, in 最終仍會在主要參與者上運行。
為了避免繼承父級的背景關系,例如,您可以將其標記為nonisolated:
nonisolated func asyncWait() async {
let continueTime: Date = Calendar.current.date(byAdding: .second, value: 2, to: Date())!
while (Date() < continueTime) {}
}
或者,您可以將函式移動到某處(例如移到 的ObservableObject外部View),該函式沒有像 那樣顯式地在主要參與者上運行View。
請注意,這里還有一些欺騙性,因為您已將函式標記為async,但它實際上并沒有做任何異步作業——它只是阻止了它正在運行的背景關系。
uj5u.com熱心網友回復:
問題是Task { ... }將任務添加到當前參與者。如果你有一些緩慢的、同步的任務,你永遠不希望主角這樣做。Task { ... }人們經常與混為一談DispatchQueue.global().async { ... },但它們不是一回事。
如果您想從當前參與者中獲得一些緩慢且同步的程序,您通常會使用Task.detached { ... }. 或者您可以為耗時的程序創建一個單獨的參與者。
但在這種情況下,沒有必要這樣做。取而代之的是 use 
轉載請註明出處,本文鏈接:https://www.uj5u.com/gongcheng/519724.html
