Task and DispatchGroup is this bad?

Let assume there is a scenario where you are attempting to convert a program to Swift Concurrency. Due to limited resources this conversion cannot happen all at once but must happen in a piecemeal fashion.

For instance, a underlying library is converted to Swift Concurrency. If I convert some of the code the async/await behavior propagates up the call chain until some point where I have to stop with a Task. And in some situations, I have to get the computed result out of the Task. Yet, to get the value out of the Task's property requires an await but I cannot do the await in a function that does not support Swift Concurrency. Seems like the only way around the issue was a reference class and DispatchGroup. Though, I've read that passing Thread/GCD stuff across a await/async boundary is very bad and to be avoided. Are there any recommended alternatives to something like the following? Thanks in advance for the suggestions and tips.

class MyClass {
var value = false
}

func mySyncFunc() -> Bool {

var result = MyClass()

let group = DispatchGroup()
group.enter()

Task.init { [result] in
    defer { group.leave() }

    func myLibraryFunc() async -> Bool  {
        return true
    }

    result.value = await myLibraryFunc()
}

group.wait()

return result.value

}

let test = mySyncFunc()

print("test = (test)")

DispatchGroup should be fine, it's DispatchSemaphore you generally want to avoid.

Generally, it's not a good idea to make async work synchronous. The basic structure you can use to adapt Swift concurrency into old school completion handlers is nested Tasks.

func doSomethingAsync(completionHandler: () -> Void) {
    Task {
        await Task {
            await someConcurrentFunc()
        }.value
        completionHandler()
    }
}
1 Like

DispatchGroup and DispatchSemaphore are the same thing under the hood. There is simply no truly good way with either Swift Concurrency or plain libdispatch (or pthreads, or any other system API on Darwin, I can’t speak to other platforms) to convert async work to sync work.

The specific consequences of doing so vary with the situation, but there are always possible consequences of some sort or another (priority inversions, spawning too many threads, spawning too few threads).

This is not a new problem, it’s far older than Swift, let alone Swift Concurrency. Swift simply picked a different set of consequences.

2 Likes

Thanks again! Very much appreciated!

Hello David,

I'm a little surprised by this answer. I'm trying to understand how leaving a dispatch group from inside a Task could cause priority inversion, spawning too many threads, or spawning too few threads, or any other adverse effect. I totally agree that waiting on a dispatch group inside a Task could be problematic, but I'm not sure why leaving is bad.

In a straightforward example I cooked up, I was unable to have this pattern result in any adverse behavior that I could observe. No warnings about priority inversion, no obviously bad things going on with threads.

Ah, you’re right, I did misread your example. In that case it depends what the nature of the waiting thread is.

1 Like