为什么可以将DispatchQueue.main.async用作RunLoop.current.run的输入源?

huangapple go评论119阅读模式
英文:

Why can DispatchQueue.main.async be used as an input source for RunLoop.current.run?

问题

import Dispatch
import Foundation

DispatchQueue.main.async {
    print("只在主队列异步中打印")
}

RunLoop.current.run(mode: .default, before: .distantFuture)

print("RunLoop.current.run 结束!")
英文:
import Dispatch
import Foundation

DispatchQueue.main.async {
    print("just print in main async")
}

RunLoop.current.run(mode: .default, before: .distantFuture)

print("RunLoop.current.run ends!")

为什么可以将DispatchQueue.main.async用作RunLoop.current.run的输入源?

If the runLoop ends after printing "just print in main async", does it means that the runLoop was terminated by main.async (just like an UI event)?

I want to know what exactly happens in this case.

答案1

得分: 3

你提出了以下问题:

为什么DispatchQueue.main.async可以用作RunLoop.current.run的输入源?

你描述的行为并不一定意味着DispatchQueue.main.async {…}在技术上是一个运行循环的“输入源”。这只是意味着run(mode:before:)可以允许libDispatch在返回之前运行分派的项。我们无法访问这些实现细节。

如果在打印“just print in main async”之后运行循环结束,是否意味着它是由main.async终止的(就像UI事件一样)?

不一定。有一些观察结果:

  • 不想纠缠细节,但当你从run(before:mode:)返回时,它并不是“终止”,而只是运行循环一次。问题只是它是否在返回之前运行分派的块。

  • 我在macOS和iOS目标以及我执行此操作的位置上看到稍微不同的行为(以及我是在@IBAction还是viewDidLoad中执行此操作等等)。这使我更加不愿对实现细节做出实质性假设。

  • 我看到了类似竞态的行为。考虑以下示例(我用“Points of Interest”标志替换了print):

    import UIKit
    import os.log
    
    let poi = OSLog(subsystem: "Test", category: .pointsOfInterest)
    
    class ViewController: UIViewController {
        @IBAction func didTapButton(_ sender: Any) {
            for i in 0 ..< 10 {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    print("after")
                    os_signpost(.event, log: poi, name: "After 1 sec", "%d", i)
                }
            }
    
            for i in 0 ..< 10 {
                DispatchQueue.main.async {
                    print("immediate", i)
                    os_signpost(.event, log: poi, name: "Immediate", "%d", i)
                }
            }
    
            RunLoop.current.run(mode: .default, before: .distantFuture)
    
            os_signpost(.event, log: poi, name: "Run")
        }
    }
    

    有时我得到:

    为什么可以将DispatchQueue.main.async用作RunLoop.current.run的输入源?

    其他时候我得到:

    为什么可以将DispatchQueue.main.async用作RunLoop.current.run的输入源?

    前者的行为确实很难复现。但是竞态通常很难显现出来。

我的底线是,在不看到RunLoop源代码或不得到苹果官方的正式保证的情况下,我会犹豫使用“运行循环输入源”这个术语与分派到队列的项相结合(GCD和运行循环是非常不同的技术栈/模式)。通常情况下,我们会使用GCD(或Swift并发等)来替代运行循环模式。此外,我会犹豫依赖于RunLoop.current.run来排空分派队列中的项。

GCD在很大程度上消除了传统运行循环的需求和(反)模式。如果这个问题纯粹是出于好奇,那么希望上述观察对您有所帮助。但如果要引入RunLoop来实现GCD中的某些功能行为,我建议在更广泛的问题上提问,并避免将RunLoop引入其中。

英文:

You asked:

> Why can DispatchQueue.main.async be used as an input source for RunLoop.current.run?

The behavior you described does not necessarily mean that DispatchQueue.main.async {…} is, technically, a runloop “input source”. It only means that the run(mode:before:) can allow libDispatch to run dispatched items before returning. We do not have access to these implementation details.

> If the runLoop ends after printing “just print in main async”, does it [mean that the runloop] was terminated by main.async (just like an UI event)?

Not necessarily. A few observations:

  • Not to split hairs, but it is not “terminated” when you return from run(before:mode:), but rather that it just runs the loop once. It is simply a question of whether it runs the dispatched blocks before returning or not.

  • I see slightly different behavior in macOS and iOS targets and where I do this (and whether I did this in an @IBAction or viewDidLoad or what have you). This makes me even more hesitant to make material assumptions about the implementation details.

  • I am seeing race-like behaviors. Consider the following (where I replaced the print with “Points of Interest” signposts):

    import UIKit
    import os.log
    
    let poi = OSLog(subsystem: &quot;Test&quot;, category: .pointsOfInterest)
    
    class ViewController: UIViewController {
        @IBAction func didTapButton(_ sender: Any) {
            for i in 0 ..&lt; 10 {
                DispatchQueue.main.asyncAfter(deadline: .now() + 1) {
                    print(&quot;after&quot;)
                    os_signpost(.event, log: poi, name: &quot;After 1 sec&quot;, &quot;%d&quot;, i)
                }
            }
    
            for i in 0 ..&lt; 10 {
                DispatchQueue.main.async {
                    print(&quot;immediate&quot;, i)
                    os_signpost(.event, log: poi, name: &quot;Immediate&quot;, &quot;%d&quot;, i)
                }
            }
    
            RunLoop.current.run(mode: .default, before: .distantFuture)
    
            os_signpost(.event, log: poi, name: &quot;Run&quot;)
        }
    }
    

    Sometimes I got:

    为什么可以将DispatchQueue.main.async用作RunLoop.current.run的输入源?

    Other times I got:

    为什么可以将DispatchQueue.main.async用作RunLoop.current.run的输入源?

    The former behavior is admittedly hard to reproduce. But races are often hard to manifest.

My bottom line is that I would hesitate to use the term “runloop input source” in conjunction with items dispatched to a queue (without seeing RunLoop source code or some formal assurances from Apple). GCD and runloops are very different tech stacks/patterns. We would generally use GCD (or Swift concurrency or what have you) in lieu of runloop patterns. Furthermore, I would hesitate to rely on a RunLoop.current.run to drain the queued items from a dispatch queue.

GCD largely eliminates the need for the legacy runloop run (anti)patterns. If the question was purely out of curiosity, then hopefully the above offers some useful observations. But if the runloop is being introduced to achieve some functional behavior in GCD, I might suggest posting a separate question on that broader question and avoid introducing RunLoop into the mix.

答案2

得分: -2

是的,我认为在提供的代码中,由于DispatchQueue.main.async块,运行循环将在打印了“just print in main async”后结束。

因为运行循环会无限期地等待事件(直到事件发生,因为您提供了.distantFuture)。

在某个时刻,一个事件会发生,它可以是用户交互事件(UI事件)或另一个正在处理的源。此事件将中断运行循环,并允许在主队列上执行计划的DispatchQueue.main.async块。

然后,“just print in main async”将从异步执行的块中打印出来。

然而,在打印的语句之后,没有安排更多的事件,所以运行循环最终会退出并打印“RunLoop.current.run ends!”。

希望这个链接也能对您有所帮助。

英文:

Yes, I think in the provided code, the run loop will end after printing "just print in main async" because of the DispatchQueue.main.async block.

As run loop will simply wait for events indefinitely (until an event occurs, because you have provided .distantFuture).

At some point, an event occurs, which can be a user interaction event (UI event) or another source getting processed. This event will interrupt the run loop and allow the scheduled DispatchQueue.main.async block to execute on the main queue.

Then "just print in main async" will be printed from the asynchronously executed block.

However, after the printed statement there are no more events scheduled, so the run loop will eventually exit and print "RunLoop.current.run ends!".

And hope this link helps, too.

huangapple
  • 本文由 发表于 2023年7月20日 17:16:26
  • 转载请务必保留本文链接:https://go.coder-hub.com/76728371.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定