3

In my program I need two tasks to run simultaneously in the background. To do that i have used concurrent queues as below,

let concurrentQueue = DispatchQueue(label: "concurrentQueue", qos: .utility, attributes: .concurrent)

concurrentQueue.async {
    for i in 0 ..< 10{
        print(i)
    }
}

concurrentQueue.async {
    for i in (0 ..< 10).reversed(){
        print(i)
    }
}

Here I need the output like this,

0
9
1
8
2
7
3
6
4
5
5
4
6
3
7
2
8
1
9
0

But what I get is,

enter image description here

I referred below tutorial in order to have some basic knowledge about Concurrent Queues in Swift 3
https://www.appcoda.com/grand-central-dispatch/

Can someone tell me what is wrong with my code? or else is it the result I should get? Is there any other ways to get my thing done? Any help would be highly appreciated.

Hanushka Suren
  • 723
  • 3
  • 10
  • 32
  • If I where to guess each async call is awaiting the work to be finished. The main difference between your code and the example is that code is still being execused in the async while the loop is being executed in the main thread. – Sam Marion Sep 25 '17 at 17:37
  • They are also doing it on two seperate queues while you're doing it on one. Create a reference to the same object and trying doing asyn on currentQueue1 – Sam Marion Sep 25 '17 at 17:40
  • 2
    It could be just a timing issue - there's no guaranty how long each operation will execute before the other kicks in. To test this, put a random sleep call in the loops to see how the output changes. Something like `usleep( arc4random() % 100000 )` – EricS Sep 25 '17 at 17:44
  • @SamMarion how is it possible? Do “for” loops run in the main thread? I didnt get it. And I have done what is exactly in the tutorial. – Hanushka Suren Sep 25 '17 at 18:06
  • @Rob Actually in my case I want the result as I mentioned. As in the tutorial it should behave like what i expected. How can it be a timing issue since two for loops are doing exactly the same thing? Yaa, they have recommended reverse DNS. But we can put our own uniq string as we need, cant we?? – Hanushka Suren Sep 25 '17 at 18:18
  • @EricS How is it possible? Two for loops do exactly the same thing. Im confused :( will try to put a sleep as you said and check. – Hanushka Suren Sep 25 '17 at 18:21
  • GMHSJ, the tutorial is misleading in showing you what the output you should expect. With uncoordinated tasks running on concurrent queues, there are simply too many variables involved to dictate what order the output is. It will vary based upon device capabilities and resources. Re reverse DNS queue labels, yes, you can use whatever string you want. (Actually Xcode didn't always correctly show queue names that weren't reverse DNS, but it looks like that is now remedied.) I was merely pointing out that reverse DNS is best practice. No offense intended. – Rob Sep 25 '17 at 18:38
  • 1
    @GMHSJ The for loops can interrupt each other while they are executing, but that doesn't mean that they WILL interrupt each other. If the loop only takes a few milliseconds, it's entirely possible for it to complete before the other loop even starts. What's even more fun is that you can run the code 1000 times in a row getting one result and then on the 1001st run you can get a different one. Makes debugging challenging. – EricS Sep 25 '17 at 22:45
  • @EricS Thanks for helping friend. Yes you are correct. Its a timing issue. After putting stuff inside for loops it worked as I wanted. – Hanushka Suren Sep 26 '17 at 05:14

2 Answers2

4

There is nothing wrong with your code sample. That is the correct syntax for submitting two tasks to a concurrent queue.

The problem is the expectation that you'd necessarily see them run concurrently. There are two issues that could affect this:

  1. The first dispatched task can run so quickly that it just happens to finish before the second task gets going. If you slow them down a bit, you'll see your concurrent behavior:

    let concurrentQueue = DispatchQueue(label: Bundle.main.bundleIdentifier! + ".concurrentQueue", qos: .utility, attributes: .concurrent)
    
    concurrentQueue.async {
        for i in 0 ..< 10 {
            print("forward: ", i)
            Thread.sleep(forTimeInterval: 0.1)
        }
    }
    
    concurrentQueue.async {
        for i in (0 ..< 10).reversed() {
            print("reversed:", i)
            Thread.sleep(forTimeInterval: 0.1)
        }
    }
    

    You'd never sleep in production code, but for pedagogical purposes, it can better illustrate the issue.

    You can also omit the sleep calls, and just increase the numbers dramatically (e.g. 1_000 or 10_000), and you might start to see concurrent processing taking place.

  2. Your device could be resource constrained, preventing it from running the code concurrently. Devices have a limited number of CPU cores to run concurrent tasks. Just because you submitted the tasks to concurrent queue, it doesn't mean the device is capable of running the two tasks at the same time. It depends upon the hardware and what else is running on that device.

    By the way, note that you might see different behavior on the simulator (which is using your Mac's CPU, which could be running many other tasks) than on a device. You might want to make sure to test this behavior on an actual device, if you're not already.


Also note that you say you "need" the output to alternate print statements between the two queues. While the screen snapshots from your tutorial suggest that this should be expected, you have absolutely no assurances that this will be the case.

If you really need them to alternate back and forth, you have to add some mechanism to coordinate them. You can use semaphores (which I'm reluctant to suggest simply because they're such a common source of problems, especially for new developers) or operation queues with dependencies.

Rob
  • 415,655
  • 72
  • 787
  • 1,044
  • Will try your suggestions and check. I understood what you are saying but i dont think it is something happening because of the device i use, in my situation. Im saying that to get this out put I dont need concurrent queues, simply i can use a global queue in which these two for loops can take place. Thanks for helping btw friend. – Hanushka Suren Sep 25 '17 at 18:59
  • After I put the stuff inside for loops it worked as I expected. yes It is a timming thing. Thanks @Rob – Hanushka Suren Sep 26 '17 at 05:06
1

May be you could try using semaphore.

    let semaphore1 = DispatchSemaphore(value: 1)
    let semaphore2 = DispatchSemaphore(value: 0)
    concurrentQueue.async {
    for i in 0 ..< 10{
    semaphore1.wait()
    print(i)
    semaphore2.signal()
    }
    }

    concurrentQueue.async {

    for i in (0 ..< 10).reversed(){
    semaphore2.wait()
    print(i)
    semaphore1.signal()
    }
    }
Shan
  • 21
  • 3