[Pitch] Async queue that serialize the excuation of it’s task in FIFO order

Last couples of days ago, I had a very intresting use case while working. I wanted to queue the call of a certain api call to make sure it’s fired in sequence because it changes the data base and it’s important to fire it in sequence.

func a() async {
  // …
  await beCall()
}

func beCall() async {
  // api call
}

Method a() can be called concurrently and can be called from more than one thread at a time but I want to make sure that the call of beCall() is excuted by order one by one.

To fix this issue, we have many approaches we can use for example AsyncStrem as a queue or even create our own queue like AsyncQueues or TaskGate.

As a solution, I like to propose a new type to be created in the stander library instead of having so many implementation for the same thing and developers don’t know which implementation to trust and use having one built in swift will be helpful.

the naming of the object is inspired from AsyncQueues we can have an object like this:

let asyncQueue = AsyncQueue()

and to queue you simply call queue method:

let asyncQueue = AsyncQueue()

func a() async {
  // …
  await asyncQueue.queue {
      await beCall()
   }
}

func beCall() async {
  // api call
}

what asyncQueue.queue does is simply it will excute await beCall() if the queue is empty and if another call happened while first task isn’t finished yet, it will queue it till it’s turn come using FIFO order.

AsyncQueue should support two important things:

  • task cancellation.
  • priority escaltion.

For the cancellation it can be configured to have two behaviors:

  1. Cancel whole queue when one task is canceled
  2. Only cancel the task that is canceled without affecting the queue

it can be configured like so:

let asyncQueue = AsyncQueue(cancellationPolicy: .cancelWholeQueue)

or

let asyncQueue = AsyncQueue(cancellationPolicy: .cancelEffectedTaskOnly)

Side note: please suggeste some names for the enum :sweat_smile:

For the second point, if a pirority escaltion happened for one task it should escalate the pirority of all the tasks in the queue, I don’t quite sure if this is the best option.

I think there is a need to have such an object in the swift lang and I think having something like that will be apprecited a lot. What do you think ?

This doesn't sound like concurrency to me. It would probably be better to use GCD or (NS)OperationQueue to get guaranteed ordering and serial execution.

2 Likes

Concurrency isn't the goal of this pitch — running things in parallel is the opposite of what I want. The goal is to run async functions in a specific FIFO order. DispatchQueue and OperationQueue can't do that here, because beCall() is async: the moment it suspends, the serial block has already "finished" from the queue's perspective, so the next one starts and the ordering is lost. That's the gap I'm proposing to fill.

I understand. The issue is that async functions are inherently concurrent. The best you can do is create a custom executor that does not allow a new task to start before the current task completes. This kind of executor is fraught with danger, because it will immediately choke on Task { while true { } }.

1 Like

(Without disagreeing, every executor will choke on while true { }. The ones this kind of executor additionally chokes on is while true { await anything() }.)

3 Likes

Using AsyncQueue is the same as this:

await beCall() // this excutes first
await beCall() // then this
await beCall() // then this

Which happens a lot in most of codebases, what AsyncQueue provides is the same thing but for functions that can be called more than once at the same time, same as DispatchQueue but for async functions.