0

I've looked through just about every question on this topic I could find but I've had little success. I need to run a function on an array of actors conforming to a specific actor protocol. Because these are actors, I need an async call. But I also need to run these functions in a specific order, and I'm not going to describe how I get the order, just suffice it to say that I have it. I am also using the following asyncForEach function, though I'm open to not doing this.

extension Sequence {
    func asyncForEach (
        _ operation: @escaping (Element) async -> Void
    ) async {
        // A task group automatically waits for all of its
        // sub-tasks to complete, while also performing those
        // tasks in parallel:
        await withTaskGroup(of: Void.self) { group in
            for element in self {
                group.addTask {
                    await operation(element)
                }
            }
        }
    }
}

Now I have some protocol

protocol ProtocolConformingActors: Actor {
    func bar() async throws
}

This leads me to running my function

func foo() async throws {
    let actorsAndOrders: [Int: ProtocolConformingActors] = [1:actor1, 2:actor2, 3:actor3]

    // Get order
    var orders: [Int] = []
    for entry in actorsAndOrders {
        orders.append(entry.key)
    }
    orders.sort()

    // Run func
    await orders.asyncForEach { order in
        let actor = actorsAndOrders[order]

        try await actor?.bar()
    }
}

And this is where the problem occurs. Like I mentioned above, these calls need to be async because bar() is modifying isolated properties on each actor. Because in order to make this happen, I need to use the asyncForEach, but as I understand it, the asyncForEach loop sets up and runs each function bar() in parallel. But they need to be run in order.

Is there a way I can make each thread wait until a condition is met?

I was thinking I might be able to use the condition orders[0] == order and when bar() is done running, remove the first entry from the orders array, which could make it tell the next thread it can wake up again.

But while the apple documentation seems to indicate that there is a wait(until:) function on NSCondition, I can't seem to make it work.

Tiny Tim
  • 207
  • 1
  • 6
  • 1
    If running the `bar`s one after another is what you actually want, why did you write `asyncForEach` which does exactly the opposite (running the `bar`s in parallel)? You should be able to just use a regular for loop to loop over `orders` and await `bar`. – Sweeper Feb 13 '23 at 08:53
  • I get the following error if I try that: `Cannot pass function of type '(Int) async -> Void' to parameter expecting synchronous function type` – Tiny Tim Feb 13 '23 at 09:00
  • 1
    Are you using the `forEach` function? Don't use that. Use a *regular for loop*. `for order in orders { try await actorsAndOrders[order]?.bar() }` Does that work? – Sweeper Feb 13 '23 at 09:02
  • And that fixed the error! Thank you! Why did that work though, what is the difference between `for order in orders` and `orders.forEach{ order in ...` – Tiny Tim Feb 13 '23 at 09:04
  • 1
    See [this answer](https://stackoverflow.com/a/73717396/5133585) – Sweeper Feb 13 '23 at 09:06
  • Fascinating! Thank you Sweeper – Tiny Tim Feb 13 '23 at 09:08
  • 1
    @TinyTim I think you might find this useful: https://gist.github.com/amomchilov/23f6c9f44fa7cebe12c2f029ea36586d – Alexander Feb 18 '23 at 02:07
  • That was very helpful! Thank you so much! I feel like I've gained a lot of strength in certain parts of swift and I recognize I'm not entirely incompetent anymore but theres still a lot of things that feel very basic that I'm lacking. Walking through the steps like you have makes it very clear what's changing and why things are changing. I think I can use this to amend other parts of my code as well, thank you! – Tiny Tim Feb 18 '23 at 06:25

0 Answers0