I'm now converting some of my codes into concurrency codes with async-await and Task
. One thing I wonder is it ok to use DispatchQueue inside Task instances like
Task {
await someHeavyStuff()
DispatchQueue.main.async {
someUIThreadStuff()
}
}
As I know Task and DispatchQueue has little different mechanism to handle asynchronous things, so I am worried that using both could mess up the threading system.
(I know that I can use MainActor.run {}
in this case)
CodePudding user response:
You get away with DispatchQueue.main.async { … }
, but you really should just retire this pattern. But if you have a big complicated project that you are slowly transitioning to Swift concurrency, and do not have the time and clean this up quite yet, yes, you can get away with this GCD call for now.
But the right solution is to just mark someUIThreadStuff
as @MainActor
and retire the DispatchQueue.main.async { … }
. It is such a trivial fix, as is MainActor.run { … }
. Of everything you might be tackling in the transition to Swift concurrency, this is one of the easiest things to just do right, and get rid of GCD API.
Where you have to be especially careful as you transition to Swift concurrency is where you use locks and semaphores or where you block the current thread. Swift concurrency can not reason about these, and those can be sources of problems. But a lingering dispatch to the main queue is unlikely to cause problems, though you should certainly excise it at your earliest convenience. See Swift concurrency: Behind the scenes, particularly the discussions about the runtime contract to never prevent forward progress.
As I look at your code snippet, I would be more concerned about the Task { … }
to start someHeavyStuff
. The name “startHeavyStuff” suggests something that is computationally expensive, blocking the current thread. But Task { … }
is for launching asynchronous tasks on current current actor, not for running “heavy” tasks on a background thread. Now, someHeavyStuff
is getting it off the current actor somehow, then ignore this caveat. But be careful you do not assume that Task { … }
will behave like DispatchQueue.global().async { … }
, because it does not.
I would suggest watching WWDC 2021 Swift concurrency: Update a sample app. It walks through a very practical exercise of refactoring your legacy code.