3

Hi I have a case where I need to call the same method in multiple Tasks. I want to have a possibility to call this method one by one (sync) not in parallel mode. It looks like that:

var isReadyToRefresh: Bool = true

func refresh(value: Int) async {
    try! await Task.sleep(nanoseconds: 100_000_000) // imitation API CALL
    isReadyToRefresh = false
    print("Try to refresh: \(value)")
}

func mockCallAPI(value: Int) async {
    if isReadyToRefresh {
        await refresh(value: value)
    }
}

Task {
     await mockCallAPI(value: 1)
}

Task {
     await mockCallAPI(value: 2)
}

output:

Try to refresh: 1

Try to refresh: 2

my required output:

Try to refresh: 1 OR Try to refresh 2. Depends which task has been called as first one.

Any ideas?

PiterPan
  • 1,760
  • 2
  • 22
  • 43

2 Answers2

7

You said:

I want [the second attempt] to wait for the first refresh API finish

You can save a reference to your Task and, if found, await it. If not found, then start the task:

actor Refresh {
    var task: Task<Void, Never>?

    func refresh(value: Int) async {
        try! await Task.sleep(nanoseconds: 100_000_000) // imitation API CALL
        print("Try to refresh: \(value)")
    }

    func mockCallAPI(value: Int) async {
        if let task = self.task {
            _ = await task.result
            return
        }

        task = Task {
            await refresh(value: value)
            task = nil
        }
    }
}

Apple showed example of this pattern in the code provided the with WWDC 2021 video, Protect mutable state with Swift actors (but this code is not on the web site; only provided in the Developer app). See How to prevent actor reentrancy resulting in duplicative requests?

Their example is more complicated (a pattern to avoid duplicate network requests from being initiated by some image cache/downloader), but the kernel of the idea is the same: Save and await the Task.

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

Why do they need to be in seperate tasks if you don't want them to run in parallel, await means the code will no progress any further than after the task completes, because of cooperative threading the thread that initiated it may be used to do something else, like process more using interaction, or other tasks, in fact because you have them in seperate task you are asking for them to be run in parallel, its possible the block that contains them will enter the very bringing and you will have another two task, in which case you need to wait on a result from them container task saying they have completed, and the code that entered the block checks this to continue any further.

Nathan Day
  • 5,981
  • 2
  • 24
  • 40
  • 1
    The OP didn't articulate the precise scenario, but from the “refresh” method name, I inferred a scenario where there are two separate events that could trigger a refresh (e.g., perhaps a push notification and a user-initiated “pull to refresh” that happen at roughly the same time). He likely doesn't want two concurrent network requests to refresh the same resource. It's a reasonable request and perfectly logical. – Rob Feb 11 '22 at 12:51