如何不派遣如此频繁?

How to do not dispatch so often?

我已经阅读了很多调度工作原理。但是我还是有点迷糊。

例如,如果我有

class ViewController: UIViewController {

    @IBAction func actionDoStuff(_ sender: UIButton) {

         DispatchQueue.global(qos: .userInitiated).async {

              Api.request { result in

                  //completes in main thread

                  //what if I need to dispatch again ?

                 DispatchQueue.global(qos: .userInitiated).async {

                     //Do other stuff here

                 }

              }

          }


     }
}

class Api {

    static func request(completion: @escaping (Result<String, NSError>) -> Void) {

        DispatchQueue.global(qos: .userInitiated).async {

        //url session configure
        let url = URL(fileURLWithPath: "test.com")
        URLSession.shared.dataTask(with: url) { data, response, error in

                DispatchQueue.main.async {

                     completion(.success("Request are success")) //without error handler for simplifier

                }

            }.resume()

        }


    }


}

所以我们有 ViewController 行动。当我们开始行动时,我们分派到全局队列并发出 Api 请求。

1 到那时(假设为第 1 点)队列是否为队列收集了线程?

然后我们进行 Api.request 调用,再次调度到全局队列。

2 它是否排队到与点 1 相同的队列?或者它排队到另一个具有相同 QoS 的队列?还是 CPU 自己做决定? 它会创建新的 Thread 吗?其实我知道 GCD 决定自己创建线程,但我不明白

然后 Api 调用完成,在某些情况下我们分派到主队列

然后我们再次调度做“//在这里做其他事情”,它会创建新队列吗?或新线程?

我也知道我们有 64 个 GCD 线程池限制。这就是我害怕的原因。我也看到 wwdc 谈论线程爆炸但不明白所以如果我们经常从一个队列调度到另一个队列是否有线程爆炸的危险?

我知道为队列创建新线程很昂贵,而且我们不需要经常从一个队列调度到另一个队列,因为我们浪费时间进行调度。

但是我的例子是这样的错误调度吗?

基本上你对队列的理解是错误的,iOS默认提供了几个调度队列(这些队列之间的唯一区别是它们保证的服务质量)和1个串行队列(显然是主队列)到每个应用程序。

当然你可以创建你自己的串行和调度队列,但是因为你的代码使用的是 dispatch.global 我将在这里坚持只使用全局队列。

无论您的应用是否访问它们,它们始终可用。这就是为什么它们被称为全局队列 :D

引用苹果

The system provides each application with four concurrent dispatch queues. These queues are global to the application and are differentiated only by their priority level. Because they are global, you do not create them explicitly.

这些是所有应用程序都可用的非引用计数对象,因此您的第一个问题“调用对全局队列进行另一次调度。”是不合逻辑的。当您访问 DispatchQueue.global(qos: 时不会创建队列,而您只访问系统中已有的几个调度队列之一,并根据您选择的 QoS 将任务添加到其中。

让我们回答您的问题,

1 At that point (let it be point 1) does queue gathered thread for the queue ?

无法猜测Queue是否已经有线程,这些是全局队列,线程是由队列自己创建、处理和管理的。因此,如果 Queue 已经有一个计划任务要执行,或者如果它已经在执行一个任务,它可能有线程,否则可能没有。多少线程?同样,我们无法控制它,调度队列决定并发执行任务需要多少线程。

2 Does it queued to same queue as at point 1 ? or it queue to the another queue with the same QoS ? or CPU make decision by itself ? And does it create new Thread ? Actually I know that GCD make decision to create thread by itself but I don't make sense

您正在访问具有相同 QoS 的全局调度队列,userInitiated 所以显然您将任务添加到您在 Point1 中使用的同一队列。我希望现在您已经知道访问 DispatchQueue.global(qos: 时不会创建队列,而只是使用 iOS.

提供的众多调度队列之一

Actually I know that GCD make decision to create thread by itself but I don't make sense

老实说,你不必这样做,这就是逻辑抽象的全部意义所在,他们编写了一个名为 GCD api 的接口来隐藏低级 api 的复杂性,例如创建线程,管理和调度它

您的代码中的问题:

很明显

static func request(completion: @escaping (Result<String, NSError>) -> Void) {

        DispatchQueue.global(qos: .userInitiated).async {

调度 API 调用 .userInitiated 队列,因此初始

 DispatchQueue.global(qos: .userInitiated).async {

              Api.request { result in

没有意义。线程上下文切换代价高昂,应仅在有意义时才进行。虽然遇到第二个语句不会再switch了,但是反正初始switch是完全没用的

DispatchQueue.global(qos: .userInitiated).async {

根据 Apple 文档

User-initiated tasks are second only to user-interactive tasks in their priority on the system. Assign this class to tasks that provide immediate results for something the user is doing, or that would prevent the user from using your app. For example, you might use this quality-of-service class to load the content of an email that you want to display to the user.

很明显,您使用 .userInitiated 队列来调度所有 api 调用是,由于缺少更好的词,我称之为滥用 userInitiated 调度队列。用户启动的任务在系统中的优先级仅次于用户交互任务。您真的希望所有长 API 通话都具有该优先级吗?如果你问我,那你走的山路非常陡峭:)

我应该用什么?

取决于你的需要,如果它的简单 API 调用你可以使用 default 全局队列,如果你需要 运行 你的 API 在后台配置你可能使用后台队列,显然不是 .userInitiated 来完成所有洗衣工作。

希望对您有所帮助

你在这里使用 dispatch 太多了。 Api.request 在调用 URLSession.shared.dataTask 之前没有任何理由派遣到 .userInitiated。那已经是一个异步调用了。在调用 Api.request.

之前,您 绝对 不应该派遣到 .userInitiated

如果您不想在 main 上执行某些操作,完成处理程序中的分派可能有意义,但它表明 Api.request 在不应该分派的时候分派。

首先,我会这样重写 request

// Accept a parameter for where you'd like to be called back; defaulting to .main.
// It is common for completion handlers to make no promise about where they're
// called (as in the case of URLSession), but it can be convenient if they do.
static func request(on queue: DispatchQueue = .main, completion: @escaping (Result<String, NSError>) -> Void) {
    let url = URL(fileURLWithPath: "test.com")
    URLSession.shared.dataTask(with: url) { data, response, error in
        queue.async {
            completion(.success("Request are success")) //without error handler for simplifier
        }

    }.resume()
}

然后这样调用它(如果你想 运行 非主队列上的完成处理程序):

@IBAction func actionDoStuff(_ sender: UIButton) {
    Api.request(on: .global(qos: .userInitiated)) { result in
        // ...
    }
}

Also I know that we have GCD thread pool limit by 64. Thats why I'm afraid of. Also I've seen wwdc talks about Thread explosion but doesn't understand so if we often dispatch from queue to queue does it danger to get thread explosion ?

如果您将许多小东西分派到并发队列(如 .global() 队列),这种情况最常发生。只要您分派到串行队列(如 .main),就不会创建额外的线程。你应该考虑创建过多的并发,但是对于这种小规模的问题(网络请求),你不应该 运行 陷入问题。网络请求非常慢;如此之慢,以至于您应该将它们视为并发性方面的永远。