Why Run Loop Is Needed When Using Dispatchqueue.Main.Async in MAC Command Line Tool in Swift

Why run loop is needed when using DispatchQueue.main.async in mac command line tool in swift?

why i should use run loop to implement task in main dispatch queue

Normally, you don’t, because you are already using one!

In an application project, there is a main queue run loop already. For example, an iOS app project is actually nothing but one gigantic call to UIApplicationMain, which provides a run loop.

That is how it is able to sit there waiting for the user to do something. The run loop is, uh, running. And looping.

But in, say, a Mac command line tool, there is no automatic run loop. It runs its main function and exits immediately. If you needed it not to do that, you would supply a run loop.

How to prevent a Command Line Tool from exiting before asynchronous operation completes

I realize this is an old question, but here is the solution I ended on. Using DispatchGroup.

let dispatchGroup = DispatchGroup()

for someItem in items {
dispatchGroup.enter()
doSomeAsyncWork(item: someItem) {
dispatchGroup.leave()
}
}

dispatchGroup.notify(queue: DispatchQueue.main) {
exit(EXIT_SUCCESS)
}
dispatchMain()

Wait until swift for loop with asynchronous network requests finishes executing

You can use dispatch groups to fire an asynchronous callback when all your requests finish.

Here's an example using dispatch groups to execute a callback asynchronously when multiple networking requests have all finished.

override func viewDidLoad() {
super.viewDidLoad()

let myGroup = DispatchGroup()

for i in 0 ..< 5 {
myGroup.enter()

Alamofire.request("https://httpbin.org/get", parameters: ["foo": "bar"]).responseJSON { response in
print("Finished request \(i)")
myGroup.leave()
}
}

myGroup.notify(queue: .main) {
print("Finished all requests.")
}
}

Output

Finished request 1
Finished request 0
Finished request 2
Finished request 3
Finished request 4
Finished all requests.

Keep command line tool alive

You need to enter into a runloop using either CFRunLoop or NSRunLoop.

Try:

[[NSRunLoop currentRunLoop] run];

Difference between DispatchSourceTimer, Timer and asyncAfter?

Timer is a Swift bridge of NSTimer, which goes back to NeXTSTEP, long, long before Grand Central Dispatch (GCD) and things like DispatchSourceTimer, which didn't come along until 10.6 (in the form of dispatch_source_set_timer) and dispatchAfter (in the form of dispatch_after).

NSTimer is based on the run loop, which was the primary way that concurrency was done until GCD. It's a cooperative concurrency system, designed primary to run on a single thread on a single core (though it can be expanded to multi-threaded environments).

While the run loop is still very important in Cocoa, it is no longer the primary, or even preferred, way to manage concurrency. Since 10.6, GCD has been the increasingly preferred approach (though adding a block-based NSTimer API in the 10.12 timeframe was a welcome modernization).

On the scale of 15 seconds, the efficiency differences are pretty irrelevant. That said, I don't understand your comment "A Timer keeps the CPU from going into the idle state." I don't believe that's true. The CPU will definitely still go into the idle state when waiting on an NSTimer to fire.

I would not set up a run loop just to run an NSTimer. You would be much better off scheduling it on the main runloop and then using DispatchQueue.async to do the actual work on some other queue.

As a broad rule, I use the highest-level tool that meets the need. Those are the ones that Apple is likely to optimize the best over time with me making the fewest changes. For example, NSTimer fire dates are automatically adjusted to improve energy efficiency. With DispatchSourceTimer, you get control over the leeway setting to get the same benefit, but it's up to you to set it (the default is zero, which has the worst energy impact). Of course, the reverse is also true. DispatchSourceTimer is the lowest level and gives you the most control, so if that's what you need, that's the one to use.

For your example, I'd personally probably use a Timer and just dispatch to the private queue as part of the block. But a DispatchSourceTimer would be completely appropriate.

asyncAfter is really a different thing, since it's always a one-shot. That's great if you want a one-shot, but it changes things if you want to repeat. If you just call asyncAfter in the block to repeat, it's going to be 15 seconds after the last time you finished, rather than being spaced 15 seconds apart. The former will tend to drift a bit late over time. The design question is this: if for some reason your task took 5 seconds to complete, would you want the next fire event to happen 15 seconds from the end of that, or would you want a constant 15 seconds between each fire event? Your choice there will determine which tool is correct.

As a slight note there, NSTimer events are always a little later than they are scheduled. GCD events with a leeway setting can be a little early or a little late. As a practical matter, there's no such thing as being "on time" (that's a period of zero length; you're not going to hit it). So the question is always whether you are promised to be late like NSTimer, or you might be early like GCD with leeway.

Code using Promises doesn't execute at all

It seems that the problem here is that all the online articles focus on iOS development, and don't really address Promises in a desktop/server executable. Simple scripts are intended to terminate, whereas mobile apps are designed to run indefinitely.

If you dig into the Thenable code, you will find that .then, .done, .catch, and so forth have an argument for a DispatchQueue, which, digging deeper, defaults to DispatchQueue.main. Because the top-level code in these examples is utilizing the main thread, the main DispatchQueue never gets an opportunity to perform the work assigned to it. Both of these examples can be easily made to execute the chain of Promises by yielding the main thread to the dispatch system by calling dispatchMain() at the very end.

However, dispatchMain() never returns, so it's not a good choice for an executable that is intended to run to completion. The better solution is to designate an alternate queue to execute each of theses promises. For a single task, we can use a semaphore in a .finally block to signal that all the work is done. For multiple tasks, I suspect you would need to use a DispatchGroup.

Here's an updated version of the first example:

import Foundation
import PromiseKit

let queue = DispatchQueue.global()
let semaphore = DispatchSemaphore(value: 0)

firstly {
Promise<String> { seal in
print("Executing closure")
seal.fulfill("Hello World!")
}
}.done(on: queue) { str in
print(str)
}.catch(on: queue) { error in
print("Error: \(error)")
}.finally(on: queue) {
semaphore.signal()
}

semaphore.wait()

And here is the working version of the second one:

import Foundation
import PromiseKit

let queue = DispatchQueue.global()
let semaphore = DispatchSemaphore(value: 0)

let url = URL(string: "https://stackoverflow.com/")!

enum SampleError: Error {
case noResponse
case badCode(Int)
case couldNotDecode
}

firstly {
Promise<Data> { seal in
URLSession.shared.dataTask(with: url) { data, response, error in
if let error = error {
return seal.reject(error)
}

guard let data = data, let response = response as? HTTPURLResponse else {
return seal.reject(SampleError.noResponse)
}

guard (200..<300).contains(response.statusCode) else {
return seal.reject(SampleError.badCode(response.statusCode))
}

seal.fulfill(data)
}.resume()
}
}.then(on: queue) { data -> Promise<String> in
Promise<String> { seal in
if let str = String(data: data, encoding: .utf8) {
seal.fulfill(str)
} else {
seal.reject(SampleError.couldNotDecode)
}
}
}.done(on: queue) { str in
print(str)
}.catch(on: queue) { error in
print("Error: \(error)")
}.finally (on: queue) {
semaphore.signal()
}

semaphore.wait()

Interestingly, the firstly block seems to execute synchronously on the main thread. It occurs to me that if you had a lot of tasks to run, it might be best to do as little work as possible inside of the firstly handler, and free up your main thread so it can spin up the tasks on the other threads more quickly.



Related Topics



Leave a reply



Submit