2

我已经阅读了很多调度如何工作。但我还是有点困惑。

例如,如果我有

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 调用完成,在某些情况下我们分派到主队列

然后我们再次派发做“//在这里做其他事情”,它会创建新的队列吗?还是新线程?

另外我知道我们的 GCD 线程池限制为 64。这就是我害怕的原因。我还看到 wwdc 谈论线程爆炸,但不明白,所以如果我们经常从队列分派到队列,线程爆炸是否危险?

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

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

4

2 回答 2

4

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

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

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

引用苹果

系统为每个应用程序提供四个并发调度队列。这些队列对于应用程序是全局的,并且仅通过它们的优先级来区分。因为它们是全局的,所以您不必显式创建它们。

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

来回答你的问题,

1 那时(让它成为第 1 点)队列是否为队列收集线程?

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

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

您正在访问具有相同 QoS 的全局调度队列,userInitiated因此显然您将任务添加到您在 Point1 中使用的同一队列中。我希望到现在你已经知道当你访问时你没有创建队列,DispatchQueue.global(qos:而你只是使用 iOS 提供的众多调度队列之一。

实际上我知道 GCD 自己决定创建线程,但我没有意义

老实说,你不必了解逻辑抽象的全部意义,他们编写了一个名为 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

没有意义。线程上下文切换代价高昂,只有在有意义时才应进行。虽然在遇到第二条语句时不会再次切换,但无论如何初始切换完全没用

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

根据苹果文档

用户发起的任务在系统上的优先级仅次于用户交互任务。将此类分配给为用户正在做的事情提供即时结果的任务,或者会阻止用户使用您的应用程序的任务。例如,您可以使用此服务质量类来加载要显示给用户的电子邮件内容。

显然,您使用 .userInitiated 队列来调度所有 api 调用是,因为缺少更好的词,我称之为滥用userInitiated调度队列。用户发起的任务在系统上的优先级仅次于用户交互任务。您真的希望所有长 API 调用都具有该优先级吗?如果你问我,你要走的山路很陡:)

我应该使用什么?

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

希望能帮助到你

于 2020-05-19T20:45:49.323 回答
4

你在这里用dispatch的太多了。在调用之前Api.request没有任何理由发送到。这已经是一个异步调用了。你绝对不应该在调用之前调度。.userInitiatedURLSession.shared.dataTask.userInitiatedApi.request

如果您不想在 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
        // ...
    }
}

另外我知道我们的 GCD 线程池限制为 64。这就是我害怕的原因。我还看到 wwdc 谈论线程爆炸,但不明白,所以如果我们经常从队列分派到队列,线程爆炸是否危险?

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

于 2020-05-19T20:45:55.777 回答