I'm trying to create an async version of XCTContext.runActivity()
. The challenge is how to make sure the async callback completes before returning to runActivity
when it's all @MainActor
.
At the moment my function looks like this:
extension XCTContext {
@MainActor
static func runAsyncActivity<Result: Sendable>(named name: String,
block: @MainActor @escaping (any XCTActivity) async throws -> Result) async throws -> Result {
try await withCheckedThrowingContinuation { continuation in
runActivity(named: name) { activity in
let semaphore = DispatchSemaphore(value: 0) // Ensure runActivity waits for the task to complete
Task {
do {
let result = try await block(activity)
continuation.resume(returning: result)
} catch {
continuation.resume(throwing: error)
}
semaphore.signal()
}
semaphore.wait()
}
}
}
}
This of course creates deadlock because semaphore.wait()
suspends the thread so that the @MainActor
child task can never run. If I use a detached task, then it will run outside the context created by runActivity
which defeats the purpose. I tried using XCTWaiter
to wait on an expectation instead of the semaphore and got the same result.
Is there a way to synchronously wait on a child task without deadlocking MainActor
? Or is it not really doable, and this is why Apple didn't provide such a function in the first place?