Multiple await/async calls that must all be on DispatchQueue.main

I have a series of async throws functions I need to call in sequence (not concurrent), and due to a restriction in a 3rd-party library these functions are using, they must run on DispatchQueue.main. The code below shows an example of what's happening. I'm still learning the concurrency stuff and would appreciate any tips. Given this code (running on IOS Simulator using Swift 5.5.2 and IOS 15.2):

func test() async {
    do {
        var isMain = Thread.isMainThread      // true here
        try await func1()                     // works fine
        isMain = Thread.isMainThread          // false here (?!?)
        try await func2()                     // If not on main thread, throws NSGenericException
        try await func3()                     // If not on main thread, throws NSGenericException
    } catch {
        // error handling
    }
}

func func1() async throws {
     // calls 3rd party library functions
}
func func2() async throws {
     // calls more 3rd party library functions, relies on result of func1()
}

These async functions must run sequentially, not concurrent. It was a surprise to me that after the return of the first call the thread has changed, but I've seen doc that indicates this is expected behavior. I don't know if there is a way to stop that from happening. Or, if there isn't, once the thread is changed how to force subsequent async calls back on DispatchQueue.main. I tried DispatchQueue.main.async { } wrappers but those can't call async functions and these functions have to stay async. I'm currently exploring if AsyncSequence or TaskGroup would help, but being a noob on Swift concurrency, haven't found a solution yet.

Any suggestions on how to solve this would be appreciated, thanks in advance :slight_smile:. I have a work-around that can hide all this from Swift, but would still like to know what the Swift solution to this issue is, if there is one.

Annotating these functions (or the type enclosing them) with @MainActor should do what you want — it will ensure all asynchronous code runs on the main thread/queue.

2 Likes