RunLoop vs DispatchQueue as Scheduler

runloop vs thread
swift runloop
swift combine delay scheduler
cfrunlooprun example
dispatchqueue main
combine dispatchqueue
runloop hang
combine receive on background thread

When using new Combine framework you can specify the scheduler on which to receive elements from the publisher.

Is there a big difference between RunLoop.main and DispatchQueue.main in this case when assigning publisher to UI element? The first one returns the run loop of the main thread and the second queue associated with the main thread.

I've posted the similar question on the Swift Forum. I encourage you to see the discussion https://forums.swift.org/t/runloop-main-or-dispatchqueue-main-when-using-combine-scheduler/26635.

I just copy and paste the answer from Philippe_Hausler

RunLoop.main as a Scheduler ends up calling RunLoop.main.perform whereas DispatchQueue.main calls DispatchQueue.main.async to do work, for practical purposes they are nearly isomorphic. The only real differential is that the RunLoop call ends up being executed in a different spot in the RunLoop callouts whereas the DispatchQueue variant will perhaps execute immediately if optimizations in libdispatch kick in. In reality you should never really see a difference tween the two.

RunLoop should be when you have a dedicated thread with a RunLoop running, DispatchQueue can be any queue scenario (and for the record please avoid running RunLoops in DispatchQueues, it causes some really gnarly resource usage...). Also it is worth noting that the DispatchQueue used as a scheduler must always be serial to adhere to the contracts of Combine's operators.

RunLoop.main or DispatchQueue.main when using Combine , main or DispatchQueue.main as the scheduler when dispatching to the main queue. .receive(on: RunLoop.main). v.s. . Use RunLoop.main, DispatchQueue.main or OperationQueue.main to perform the UI-related work. As specified in this thread , there is no difference between them. Aside from ImmediateScheduler , Combine does not introduce any new scheduler types.

I saw the response posted by Roy and thought I could use them interchangeably, but I actually noticed a big difference in my app.

I was loading an image asyncronously in a custom table view cell. Using RunLoop.main would block images from loading as long as the table view was scrolling.

  subscriber = NetworkController.fetchImage(url: searchResult.artworkURL)
    .receive(on: RunLoop.main)
    .replaceError(with: #imageLiteral(resourceName: "PlaceholderArtwork"))
    .assign(to: \.image, on: artworkImageView)

But switching to DispatchQueue.main allowed the images to load while it was scrolling.

  subscriber = NetworkController.fetchImage(url: searchResult.artworkURL)
    .receive(on: DispatchQueue.main)
    .replaceError(with: #imageLiteral(resourceName: "PlaceholderArtwork"))
    .assign(to: \.image, on: artworkImageView)

Understanding Schedulers in Swift Combine Framework, The where means current run loop, dispatch queue or operation queue. The when means virtual time, according to the scheduler's clock. The� The Combine framework provides the Scheduler protocol, which is a powerful abstraction for describing how and when units of work are executed. It unifies many disparate ways of executing work, such as DispatchQueue, RunLoop and OperationQueue. However, the Combine framework is still quite new and

There actually is a big difference between using RunLoop.main as a Scheduler and using DispatchQueue.main as a Scheduler.

We can see the implementation of RunLoop's conformance to Scheduler in Schedulers+RunLoop.swift. In particular, here's how it implements schedule(options:_:):

    public func schedule(options: SchedulerOptions?,
                         _ action: @escaping () -> Void) {
        self.perform(action)
    }

This uses the RunLoop perform(_:) method, which is the Objective-C method -[NSRunLoop performBlock:]. The performBlock: method schedules the block to run in the default run loop mode only. (This is not documented.)

UIKit and AppKit run the run loop in the default mode when idle. But, in particular, when tracking a user interaction (like a touch or a mouse button press), they run the run loop in a different, non-default mode. So a Combine pipeline that uses receive(on: RunLoop.main) will not deliver signals while the user is touching or dragging.

We can see the implementation of DispatchQueue's conformance to Scheduler in Schedulers+DispatchQueue.swift. Here's how it implements schedule(options:_:):

    public func schedule(options: SchedulerOptions?, _ action: @escaping () -> Void) {
        let qos = options?.qos ?? .unspecified
        let flags = options?.flags ?? []

        if let group = options?.group {
            // Distinguish on the group because it appears to not be a call-through like the others. This may need to be adjusted.
            self.async(group: group, qos: qos, flags: flags, execute: action)
        } else {
            self.async(qos: qos, flags: flags, execute: action)
        }
    }

So the block gets added to the queue using a standard GCD method, async(group:qos:flags:execute:). Under what circumstances are blocks on the main queue executed? In a normal UIKit or AppKit app, the main run loop is responsible for draining the main queue. We can find the run loop implementation in CFRunLoop.c. The important function is __CFRunLoopRun, which is much too big to quote in its entirety. Here are the lines of interest:

#if __HAS_DISPATCH__
    __CFPort dispatchPort = CFPORT_NULL;
    Boolean libdispatchQSafe =
        pthread_main_np()
        && (
            (HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && NULL == previousMode)
           || (!HANDLE_DISPATCH_ON_BASE_INVOCATION_ONLY && 0 == _CFGetTSD(__CFTSDKeyIsInGCDMainQ))
        );
    if (
        libdispatchQSafe
        && (CFRunLoopGetMain() == rl)
        && CFSetContainsValue(rl->_commonModes, rlm->_name)
    )
        dispatchPort = _dispatch_get_main_queue_port_4CF();
#endif

(I have wrapped the original source lines for readability.) Here's what that code does: if it's safe to drain the main queue, and it's the main run loop, and it's a .common mode, then CFRunLoopRun will check for the main queue being ready to drain. Otherwise, it will not check and so it will not drain the main queue.

The .common modes include the tracking modes. So a Combine pipeline that uses receive(on: DispatchQueue.main) will deliver signals while the user is touching or dragging.

concurrent queue, RunLoop.main and DispatchQueue.main, � Issue , There is an interesting discussion about the difference between RunLoop.main or DispatchQueue.main RunLoop.main as a Scheduler ends up� Swift Nov 12, 2019 Apr 10, 2020 • 5 min read Concurrent vs Serial DispatchQueue: Concurrency in Swift explained. Concurrent and Serial queues help us to manage how we execute tasks and help to make our applications run faster, more efficiently, and with improved responsiveness.

An important caveat of RunLoop is that it is "not really thread safe" (see https://developer.apple.com/documentation/foundation/runloop), so it can be used to delay execution of blocks but not to dispatch them from another thread. If you are doing multithread work (such as loading an image asynchronously) you should be using a DispatchQueue to go back to your main UI thread

Run Loops, to your run loop, and is shown in Listing 3-4. Because this input source has only one client (the main thread), it uses the scheduler function to� Sử dụng RunLoop.main, DispatchQueue.main hoặc OperationQueue.main để thực hiện các công việc liên quan đến UI. Ngoài ImmediateScheduler, Combine không giới thiệu bất kỳ loại scheduler mới nào cả. Thay vào đó, nó mở rộng các API đa luồng trong Swift để trở thành scheduler. Scheduler mặc

receive(on:options:), Specifies the scheduler on which to receive elements from the publisher. while the receive(on:options:) causes labelUpdater to receive elements and completion on RunLoop.main . pub.sink { DispatchQueue.main.async { // Do something. }� Key Difference – Scheduler vs Dispatcher. Scheduler and Dispatcher are associated with process scheduling of an operating system. The key difference between scheduler and dispatcher is that the scheduler selects a process out of several processes to be executed while the dispatcher allocates the CPU for the selected process by the scheduler.

Concurrent vs Serial DispatchQueue: Concurrency in Swift explained, The differences between concurrent and serial dispatchqueue explained in Swift. How to use the main thread, background thread and prevent� DispatchQueue Language: Swift API Changes: Show Class Dispatch Queue. An object that manages the execution of tasks serially or concurrently on your app's main thread

Event Loop, Run Loop, Programs with and without it, The event loop is a more general term and run loop is the particular application that uses an event loop for different input sources and task scheduling? The thing is that the sync method of a DispatchQueue always uses� Scheduler is something which selects a process among various processes: Types: There are no different types in dispatcher.It is just a code segment. There are 3 types of scheduler i.e. Long-term, Short-term, Medium-term: Dependency: Working of dispatcher is dependent on scheduler.Means dispatcher have to wait until scheduler selects a process.

Comments
  • That's a huge caveat, and explains why my image loading only worked with DispatchQueue. Thanks for sharing!
  • Apple docs are inconsistent, because here developer.apple.com/library/archive/documentation/Cocoa/… they recommend using RunLoop to dispatch stuff around threads. Since they say two opposite things at the same time, the Apple doc too is not really thread safe...
  • @landonepps Not all of the RunLoop API is thread-safe, but the methods that give it conformance to Combine.Scheduler are thread-safe. (The conformance would be nigh-useless otherwise.) The reason your image loading works with DispatchQueue but not RunLoop is because RunLoop's Scheduler conformance schedules blocks to run only in the default run loop mode, but UIKit (and AppKit) run the run loop in a non-default mode while tracking a scroll gesture.