英文:
Converting DispatchQueue.global().async to async/await for synchronous function
问题
I'm converting my old project from DispatchQueue to async await, there's a time-consuming operation which I wrapped in the global dispatch queue:
DispatchQueue.global().async {
self.processed = processData(input)
DispatchQueue.main.async {
render() // reload UI with self.processed
}
}
processData()
is a time-consuming synchronous operation.render()
updates UI with processed data and needs to be on the main thread.
It seems like the closest thing to a global queue is to use Task.detached
, but when using it, I can't mutate main actor-isolated properties like self.processed
.
Then I thought about doing this:
processData(input: Input) async -> Output {
await withCheckedContinuation { continuation in
DispatchQueue.global().async {
let output = process(input)
continuation.resume(returning: output)
}
}
}
…
let processed = await processData(input)
render()
But it feels like I'm using async/await just for the sake of it while still using DispatchQueue
. Any thoughts? Thanks!
英文:
I'm converting my old project from DispatchQueue to async await, there's a time consuming operation which I wrapped in the global dispatch queue:
DispatchQueue.global().async {
self.processed = processData(input)
DispatchQueue.main.async {
render() // reload UI with self.processed
}
}
processData()
is a time consuming synchronous operationrender()
updates UI with processed data, and it need to be on main thread
Looks like the closest thing to global queue is to use Task.detached
, but using it I can't mutate main actor-isolated properties e.g. self.processed
.
Then I thought about doing this:
processData(input: Input) async -> Output {
await withCheckedContinuation { continuation in
DispatchQueue.global().async {
let output = process(input)
continuation.resume(returning: output)
}
}
}
…
let processed = await processData(input)
render()
But it feels like doing it for the sake for using async/await while still use DispatchQueue
. Any thoughts? Thanks!
答案1
得分: 0
你可以像这样使用具有后台优先级的任务:
Task.detached(priority: .background) {
let output = await processData(input: yourInput)
await MainActor.run {
render()
}
}
func processData(input: Input) async -> Output {
// 进行耗时处理
}
英文:
You can use Task with background priority likewise:
Task.detached(priority: .background) {
let output = await processData(input:yourInput)
await MainActor.run {
render()
}
}
func processData(input: Input) async -> Output {
//do your expensive processing
}
答案2
得分: 0
Yes, you theoretically can use a detached task.
Just make sure that the method which has this code is isolated to the main actor, as well as the render
method, but that the processData
method is not. E.g., in a type that is, itself, is actor isolated then you just need to mark processData
as nonisolated
:
@MainActor
final class Foo: Sendable {
var processed: Output?
func processAndUpdateUI(for input: Input) async throws {
processed = try await Task.detached {
try self.processData(input)
}.value
render()
}
func render() {…}
nonisolated func processData(_ input: Input) throws -> Output {…}
}
But I would explicitly advise against the MainActor.run {…}
or Task { @MainActor in …}
patterns. That is brittle. The burden for getting onto the right actor should not fall on the shoulders of the caller. Just make sure that the relevant methods and properties are actor-isolated, and the compiler will ensure that you call these methods correctly, that values crossing actor-isolation boundaries are Sendable
, etc.
A note on this latter point. When you pass objects between threads, you have to let the compiler know that they are thread-safe, i.e., Sendable
. See WWDC 2022 video Eliminate data races using Swift Concurrency. In Swift 5.x, it will not always warn you if your types are Sendable
or not, so consider turning changing the “Strict Concurrency Checking“ build setting to “Complete”:
The first time or two you deal with Sendable
types, it can seem awfully confusing, but hopefully the above videos will be helpful. But once you master Sendable
conformance, it will become second nature, and you’ll wonder why you suffered through all those thread-safety headaches of yesteryear.
The problem is that detached tasks are unstructured concurrency. I.e., if you cancel the parent task, it will not propagate the cancellation to the child tasks.
So, I might remain within structured concurrency. E.g., I might move processData
into its own actor:
@MainActor
final class Bar: Sendable {
private let processor = Processor()
var processed: Output?
func processAndUpdateUI(for input: Input) async throws {
processed = try await processor.processData(input)
render()
}
func render() {…}
}
actor Processor {
func processData(_ input: Input) throws -> Output {
while … {
try Task.checkCancellation() // periodically check to make sure this hasn’t been canceled
…
}
return output
}
}
英文:
Yes, you theoretically can use a detached task.
Just make sure that the method which has this code is isolated to the main actor, as well as the render
method, but that the processData
method is not. E.g., in a type that is, itself, is actor isolated then you just need to mark processData
as nonisolated
:
@MainActor
final class Foo: Sendable {
var processed: Output?
func processAndUpdateUI(for input: Input) async throws {
processed = try await Task.detached {
try self.processData(input)
}.value
render()
}
func render() {…}
nonisolated func processData(_ input: Input) throws -> Output {…}
}
But I would explicitly advise against the MainActor.run {…}
or Task { @MainActor in …}
patterns. That is brittle. The burden for getting onto the right actor should not fall on the shoulders of the caller. Just make sure that the relevant methods and properties are actor-isolated and the compiler will ensure that you call these methods correctly, that values crossing actor-isolation boundaries are Sendable
, etc.
A note on this latter point. When you pass objects between threads, you have to let the compiler know that they are thread-safe, i.e., Sendable
. See WWDC 2022 video Eliminate data races using Swift Concurrency. In Swift 5.x, it will not always warn you if your types are Sendable
or not, so consider turning changing the “Strict Concurrency Checking“ build setting to “Complete”:
The first time or two you deal with Sendable
types, it can seem awfully confusing, but hopefully the above videos will be helpful. But once you master Sendable
conformance, it will become second nature and you’ll wonder why you suffered through all those thread-safety headaches of yesteryear.
The problem is that detached tasks are unstructured concurrency. I.e., if you cancel the parent task, it will not propagate the cancelation to the child tasks.
So, I might remain within structured concurrency. E.g., I might move processData
into its own actor:
@MainActor
final class Bar: Sendable {
private let processor = Processor()
var processed: Output?
func processAndUpdateUI(for input: Input) async throws {
processed = try await processor.processData(input)
render()
}
func render() {…}
}
actor Processor {
func processData(_ input: Input) throws -> Output {
while … {
try Task.checkCancellation() // periodically check to make sure this hasn’t been canceled
…
}
return output
}
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论