2

i saw this thread Swift 5.5 Concurrency: how to serialize async Tasks to replace an OperationQueue with maxConcurrentOperationCount = 1? but i am not clear on whether two scheduled Tasks will execute serially. What i mean is, if i had a piece of code

func fetchImages() {
   Task.init {
     let fetch = await loadImages()
   }

   Task.init {
     let fetch1 = await loadImages1()
   }
}

will first task always finish before second task starts? The application i am trying to get is to execute task 1 as soon as possible (save time) but task 2 relies on the result of task 1 so it needs to wait for task 1 to finish before proceeding. Task 2 also is only conditionally triggered upon an event so they cannot be in the same Task.

infoMining
  • 91
  • 7
  • Check this article: https://www.swiftbysundell.com/articles/task-based-concurrency-in-swift/ (the Sequencing portion of it). You will still have to adopt that solution to accommodate your "Task 2 also is only conditionally triggered upon an event" condition. I.e. maybe you will have task in a sequence, but cancel it if not needed, or maybe you will need a dynamic sequence. – timbre timbre May 25 '22 at 20:34

1 Answers1

4

You asked:

will first task always finish before second task starts?

No, it will not. Whenever you see await, that is a “suspension point” at which Swift concurrency is free to switch to another task. In short, these can run concurrently. Let me illustrate that with Xcode Instruments:

import os.log

private let log = OSLog(subsystem: "Test", category: .pointsOfInterest)

class Foo {
    func fetchImages() {
        Task {
            let fetch = await loadImages()
            print("done loadImages")
        }
        
        Task {
            let fetch1 = await loadImages1()
            print("done loadImages1")
        }
    }
    
    func loadImages() async {
        // start “points of interest” interval

        let id = OSSignpostID(log: log)
        os_signpost(.begin, log: log, name: #function, signpostID: id, "start")

        // perform 3-second asynchronous task

        try? await Task.sleep(nanoseconds: 3 * NSEC_PER_SEC)

        // end “points of interest” interval

        os_signpost(.end, log: log, name: #function, signpostID: id, "end")
    }

    func loadImages1() async {
        // start “points of interest” interval

        let id = OSSignpostID(log: log)
        os_signpost(.begin, log: log, name: #function, signpostID: id, "start")

        // perform 1-second asynchronous task

        try? await Task.sleep(nanoseconds: 1 * NSEC_PER_SEC)

        // end “points of interest” interval

        os_signpost(.end, log: log, name: #function, signpostID: id, "end")
    }
}

Profiling (in Xcode, either press command-i or choose from the menu, “Product” » “Profile”) this with the “time profiler” in Instruments, you can see that these run concurrently:

enter image description here


The trick is to have the second task await the first one. E.g.,

func fetchImages() {
    let firstTask = Task {
        let fetch = await loadImages()
        print("done loadImages")
    }
    
    Task {
        _ = await firstTask.result
        let fetch1 = await loadImages1()
        print("done loadImages1")
    }
}

Or you can store the first task in some property:

var firstTask: Task<Void, Never>?   // you may need to adjust the `Success` and `Failure` types to match your real example

func fetchImages() {
    firstTask = Task {
        let fetch = await loadImages()
        print("done loadImages")
    }
    
    Task {
        _ = await firstTask?.result
        let fetch1 = await loadImages1()
        print("done loadImages1")
    }
}

When you do that, you can see the sequential execution:

enter image description here

FWIW, this concept, of using await on the prior task was the motivating idea behind that other answer that you referenced. That is a more generalized rendition of the above. Hopefully this illustrates the mechanism outlined in that other answer.

Rob
  • 415,655
  • 72
  • 787
  • 1,044