Swift async/await 是什么 DispatchQueue.main.async 的替代品

Swift async/await what it the replacement of DispatchQueue.main.async

提问人:Michał Ziobro 提问时间:2/11/2022 最后编辑:Michał Ziobro 更新时间:8/10/2022 访问量:6649

问:

在新的 Swift 5.5 中使用 async/await 并发机制时如何返回主线程?我应该只用@MainActor标记函数、类吗? 我还能使用吗?这是正确的吗?由于新机制不使用 GCD,并且异步任务和线程之间没有像以前那样的映射?DispatchQueue.main.async

例如,我将 SwiftUI 与Listrefreshable

List { }
.refreshable {
    viewModel.fetchData()
}

这样可以吗

List { }
.refreshable {
    DispatchQueue.main.async {
      viewModel.fetchData()
    }
}

或者我需要在 ViewModel 类上添加@MainActor? 我没有在项目中使用 async/await,所以仅将 MainActor 用于这个单一的刷新对象似乎是多余的,我也不知道添加这样的属性如何影响 ViewModel 类的其余方法和属性,他们现在使用 Combine。

但另一方面,Xcode 显示

运行时:SwiftUI:不从后台线程发布更改 允许;确保从主线程发布值(通过 像 receive(on:)) 这样的运算符在模型更新时。

此外,在将@MainActor添加到 ViewModel 后,我收到多个这样的警告

与全局参与者“MainActor”隔离的属性“title”无法满足 协议的相应要求 “OnlineBankingListViewModelProtocol”(网上银行ListViewModelProtocol)

swift 异步等待

评论


答:

0赞 Nathan Day 2/11/2022 #1

在很多情况下,新的异步模型会计算出哪个线程最适合执行任务,如果你的任务可以访问 Guide 对象,编译器/运行时会选择在主线程中运行整个任务,如果你有其他东西想在主线程中运行, 你可以使用 @ MainActor,但是你在主线程中强制的东西越多,你在该线程上投入的工作就越多,而分配给其他线程的工作就越少,因此你的工作分散在多个内核上的机会就越少。如果你真的推动它,你可以得到一个任务在后台线程中运行,访问指南的东西,你会发现执行将跳转到主线程来执行 GUI 调用。如果你想在主线程中运行一组调用,并使用一些你编码的 gui 代码,只需将它们包装在一个任务中,运行时系统就会为你排序。

13赞 Rob 2/12/2022 #2

你问:

我还能使用吗?DispatchQueue.main.async

如果你在一个方法中,并且想要将一些东西分派到主队列,那么最字面上的等价物是:async

MainActor.run { ... }

但更谨慎的做法是简单地用 标记方法(或其类)。这不仅可以确保它在主线程上运行它,而且如果您尝试从错误的参与者调用它,您会收到编译时警告。@MainActor

因此,如果您的视图模型标有 ,则无需手动运行任务。在处理被观察对象的已发布属性时尤其如此。@MainActorMainActor

例如,请考虑:

@MainActor
class ViewModel: ObservableObject {
    @Published var values: [Int] = []

    func fetchData() async {
        let foo = await ...
        values = foo.values
    }
}

然后

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        List {
            ...
        }
        .refreshable {
            await viewModel.fetchData()
        }

    }
}

(请注意,我创建了一个方法,并在其中进行了调整,以便微调器准确反映进程何时运行。fetchDataasyncawaitrefreshableasync

观看 WWDC 2021 视频 Swift 并发:更新示例应用。诚然,这说明了 UIKit 应用程序的过渡,但包括 和 的示例。@MainActorMainActor.run


请注意,虽然 在很大程度上消除了 的需要,但在某些情况下,您可能会使用此模式。具体来说,如果你在某个其他 actor 上,并且想要在主线程上连续运行三个单独的函数,你可以将它们的系列包装在一个块中,从而通过对主 actor 的单个调度来运行所有三个函数,而不是三个单独的调用。@MainActorMainActor.run { … }run@MainActorMainActor.run { … }


上面,我重点介绍了突出的部分,但这是我的完整 MCVE:

struct ContentView: View {
    @ObservedObject var viewModel = ViewModel()

    var body: some View {
        List {
            ForEach(viewModel.values, id: \.self) { value in
                Text("\(value)")
            }
        }
        .refreshable {
            await viewModel.fetchData()
        }

    }
}

struct Foo: Decodable{
    let json: [Int]
}

@MainActor
class ViewModel: ObservableObject {
    @Published var values: [Int] = []

    func fetchData() async {
        do {
            let foo = try await object(Foo.self, for: request)
            values = foo.json
        } catch {
            print(error)
        }
    }

    func object<T: Decodable>(_ type: T.Type, for request: URLRequest) async throws -> T {
        let (data, response) = try await URLSession.shared.data(for: request)

        guard let response = response as? HTTPURLResponse else {
            throw URLError(.badServerResponse)
        }

        guard 200 ... 299 ~= response.statusCode else {
            throw ApiError.failure(response.statusCode, data)
        }

        return try JSONDecoder().decode(T.self, from: data)
    }

    var request: URLRequest = {
        let url = URL(string: "https://httpbin.org/anything")!
        var request = URLRequest(url: url)
        request.httpMethod = "POST"
        request.httpBody = "[1,2,3,4,5]".data(using: .utf8)
        request.addValue("application/json", forHTTPHeaderField: "Content-Type")
        request.addValue("application/json", forHTTPHeaderField: "Accept")

        return request
    }()
}

enum ApiError: Error {
    case failure(Int, Data)
}

评论

3赞 Soumya Mahunt 8/21/2022
这应该是公认的答案。不仅准确描述了 OP 要求的内容,还提供了编译时检查的其他最佳实践。
8赞 scaly 8/10/2022 #3

替代的是:DispatchQueue.main.async { foo.bar() }

Task { @MainActor in 
    print(Thread.current.isMainThread) // "true"
}