-1
let serialQueue = DispatchQueue(label: "Serial Queue")
func performCriticalSectionTask() {
 serialQueue.async {
   performLongRuningAsyncTask()
 }
}

func performLongRuningAsyncTask() {
  /// some long running task
}

The function performCriticalSectionTask() can be called from different places many times. I want this function to be running one at a time. Thus, I kept the critical section of code inside the serial async queue.

But, the problem here is that the critical section itself is a performLongRuningAsyncTask() which will return immediately, and thus serial queue will not wait for the current task to complete first and will start another one.

How can I solve this problem?

SandeepAggarwal
  • 1,273
  • 3
  • 14
  • 38
  • All you’re synchronizing here is the dispatching of this long running asynchronous task, but not the running of the task. There are a couple of solutions: You could make the long running task synchronous (though it’s hard to say whether that’s prudent without knowing what this long running async task is doing). You could wrap this asynchronous task in a custom, asynchronous, `Operation` subclass, only completing the operation when the asynchronous task is done. You could use Combine. If you can give us a hint about what this long running asynchronous task is, we can probably better advise you. – Rob Sep 07 '20 at 19:04
  • Hey @Rob the long running task is a networking request – SandeepAggarwal Sep 07 '20 at 20:58
  • I thought of operation subclass as well but that would be too much for a single network request. I wanted to do simple solution for this if there is any. – SandeepAggarwal Sep 07 '20 at 21:02
  • Yeah, I'd probably just do `Operation` subclass. It's designed to handle this asynchronous dependency pattern nicely. E.g. https://stackoverflow.com/a/32322851/1271826. By the way, are you 100% sure you want to force network requests to be sequential? You pay a huge performance penalty when you do that. Make them sequential where absolutely needed, but make them concurrent whenever you can. – Rob Sep 07 '20 at 21:19

1 Answers1

0

if performLongRuningAsyncTask is only running in one thread, it will be called only once at the time. In your case it delegates it to another thread, so you wrapping it into another thread call doesn't work since it will be on another thread anyway

You could do checks in the method itself, the simplest way is to add a boolean. (Or you could add these checks in your class that executes this method, with a completion handler). Another ways are adding dispatch groups / semaphores / locks. If you still need it to be executed later, you should use a dispatch group / OperationQueue / Semaphore.

func performLongRunningAsyncTask() {
   self.serialQueue.sync {
       if isAlreadyRunning {
          return 
       }

       isAlreadyRunning = true
   }

   asyncTask { result in 

     self.serialQueue.sync {
        self.isAlreadyRunning = false
     }
   }
}
Vitalii Shvetsov
  • 404
  • 2
  • 11