1

I've written a global function that allows me to synchronously wait for the completion of an async task (for experimentation purposes only!) with the help of a DispatchSemaphore.

///
/// synchronously waits for the completion of an asynchronous closure
/// - parameter handler: the task to run, asynchronously 
/// - note: don't use this in production code, it violates
/// the Swift runtime contract of "forward" progress, it's never
/// safe to block a thread and wait for another thread to unblock it
/// (on a single core system, this may hang forever)
///
func sync(handler: @escaping () async -> Void) {
    let sema = DispatchSemaphore(value: 0)
    // default `Task.Priority` to `asyncDetached`  is `nil`
    Task.detached {
        await handler()
        sema.signal()
    }
    // blocks the current thread, waiting for the async Task to finish
    sema.wait()
}

I noticed that if I just use an Task block to launch the asynchronous tasks a deadlock arises, presumably because sema.wait() is blocking the current thread, preventing the Task block from ever running (as normal Task blocks seem to inherit the current thread), so it will just wait forever.

Using Task.detached does not seem to block the thread, making the above code work. This appears to be related to the Task.Priority? to which the detached call is assigned. The default parameter value is to which is nil when calling Task.detached. This can be customized to a different priority level, indicating the dispatch QoS.

My questions therefore is: what thread does Task.Priority? = nil relate to? It does not appear to ever be the same thread as where it was launched from. Does Task.Priority? = nil indicate to the Swift runtime to always run on a different thread?

EDIT

Follow on question - how do Task and Task.detached differ in regard to threading?

Bradley Mackey
  • 6,777
  • 5
  • 31
  • 45

2 Answers2

1

The priority does not influence the priority on your app's side. It is a hint for the host to response to several requests in a priority. The host can completely ignore that.

refer to this answer for more information.

Therefore task priority has nothing to do with the thread with that aside. you'r task is getting dispatched every time and Task.Priority refers to no thread and has nothing to do with threads.

Mohmmad S
  • 5,001
  • 4
  • 18
  • 50
-1

Rather than using semaphores and asyncDetached, I suggest using GCD queues and DispatchGroups. They are lightweight and very easy to use. You just

  • Create a DispatchGroup (let group = DispatchGroup(), call group.notify(queue:work:) to submit a block to be run when the group of tasks is complete.
  • Call group.enter() each time you begin a task you want to wait for,
  • Call group.leave() when you complete a task

The system will then invoke the block you submitted in your notify(queue:work:) call once the tasks are complete. That's all there is to it. You can submit 0, one, or many tasks. If you haven't submitted any, or they have all completed, your notify closure fires immediately. If some tasks are still running, your notify closure isn't called until your tasks call group.leave()

Duncan C
  • 128,072
  • 22
  • 173
  • 272
  • This would be an acceptable use case in reality, but in my sample I actually *want* the code to block it's thread until the work completes (on another thread) and then continue synchronously, rather than being notified in an async `notify` block. I could replace `DispatchSemaphore` with `DispatchGroup` in my example, but the outcome would be the same in this case. – Bradley Mackey Jun 09 '21 at 14:09
  • What's the point of synchronous code running on multiple threads? You don't seem to gain any benefit over doing everything on a single thread, and it's more complex to read and maintain. – Duncan C Jun 09 '21 at 18:40
  • And why the down-vote? This seems like an A/B question. – Duncan C Jun 09 '21 at 18:40
  • (Not my downvote). The issue is that Swift async/await code cannot be executed synchronously, it has to happen in an `async` context. Async code is required when dealing with `actors` for example (which isolate and manage access through async/await), so there are real use cases where an `async` block may want to be dispatched synchronously (when starting from a synchronous context, for example). – Bradley Mackey Jun 09 '21 at 20:35