How to asynchronously call 2 functions in an actor

I want to call 2 functions in parallel in an actor without waiting for either one to complete. They are both changing different items in the actor so that both are not going to break any protected state. I still also want the actor to protect the mutable state when other objects call this actor. Based on my understanding of what an actor is, functions in an actor are synchronous and are always called one after the other to protect the state of the actor. Is the below code correct? Or is it the case that even with the async lets there is no way to call the actor's function in parallel

Actor NewActor {
var item1: Int = 0
var item2: Int = 1

func changeInt1() async -> Int {
 item1 = 2
return Item1
}

func changeInt2() async -> Int {
 item2 = 3
return item2
}
}

let newActor = NewActor()
async let item1 = newActor.changeInt1()
async let item2 = newActor.changeInt2()

await let _ = item1
await let _ = item2

That's correct. An actor has a message queue containing all the pending requests (method invocations) sent to the actor, which processes the messages in its queue sequentially.

1 Like

Right, an actor never executes multiple pieces of (isolated) functions in parallel; that's how they work, and how they achieve the thread safety guarantees.

Note also tha this:

async let item1 = newActor.changeInt1()
async let item2 = newActor.changeInt2()

is very problematic because it doesn't even guarantee that the actor first gets changeInt1 and then changeInt2 so if you needed ordering guarnatee between that 1 and 2, you just lost it... (Since this is equivalent to "new child task { await newActor.changeInt1() }" and "new child task { await newActor.changeInt2() } where those child tasks are scheduled independently and the second may run first...). Offering some API to "send off those two things to the actor to work on, in this order, has been discussed but we lack a solution today.

Realistically, the only correct way to do this is: await newActor.changeInt1() and then await the other call.

3 Likes

This is related but not exactly identical in terms of use case (if you just care about ordering, but not the actual return value - assuming that if a send returns it has been enqueued in the mbox):

:slightly_smiling_face:

I'm, personally, very interested in a send-like operation... it has to be explored thoroughly though, we'll see what we can come up with to address this family of issues :slight_smile:

3 Likes

"is very problematic because it doesn't even guarantee that the actor first gets changeInt1 and then changeInt2" This is exactly the behavior I want. both change 1 and change 2 mutate different variables in the actor. So it means I can have parallel calls on functions in the same actor?

Yes you can, but they will be executed sequentially in any order as @ktoso explained.

No, it does not: both calls will execute without ordering, but one-at-a-time. So they could execute change Int1 then changeInt2, or changeInt2 then changeInt1, but both will not execute at the same time. Actors protect all their data.

In that case is the solution to have 2 actors?

Actor Actor1 {
  var item1: Int = 0
  func changeInt1() async -> Int {
     item1 = 2
     return Item1
  }
}

Actor Actor2{
  var item2: Int = 0
  func changeInt2() async -> Int {
     item2 = 2
     return Item2
  }
}

let newActor1 = Actor1()
let newActor2 = Actor2()
async let item1 = newActor1.changeInt1()
async let item2 = newActor2.changeInt2()

await let _ = item1
await let _ = item2
1 Like

Yeah that's one way to do it; An actor is a thing that serializes accesses, so if you want two things happening in parallel, they'd have to be in separate actors.

Others have correctly noted that this is not true, but if you mean concurrent instead of parallel, it would be correct. The first function could suspend on an await and then the other function could make progress without needing to wait until the first function is completely finished.

The answer to this question depends on what you do inside those async methods and your definition of parallel here.

let newActor = NewActor()
async let item1 = newActor.changeInt1()
async let item2 = newActor.changeInt2()

await let _ = item1
await let _ = item2

Since both changeInt1 and changeInt2 are async functions, based on what they await, changeInt2 could start running when changeInt1 is awaiting.

An actor serialises access to its isolated properties but that doesn’t mean that an actor-isolated method runs from start to end without interruption

The above comment is taken from this nice answer in Apple forum. Read that.

https://forums.developer.apple.com/forums/thread/713510

This code has two awaits, explicitly serializing things. One await will make code more "parallel":

await let _ = [item1, item2]

But indeed, the answer depends on definition of "parallel", and actual execution will be serial anyway.