Блок в методе синхронизации в фоновой очереди выполняется в основном потоке - PullRequest
1 голос
/ 05 марта 2019

Я только начал изучать GCD, и у меня возникают проблемы, потому что мой код все еще выполняется в главном потоке, пока я создаю фоновую очередь. Это мой код:

import UIKit

class ViewController: UIViewController {

    let queue = DispatchQueue(label: "internalqueue", qos: .background)

    override func viewDidLoad() {
        super.viewDidLoad()

        dispatchFun {
            assert(Thread.isMainThread)

            let x = UIView()
        }
    }

    func dispatchFun(handler: @escaping (() -> ())) {
        queue.sync {
            handler()
        }
    }
}

Довольно удивительно (для меня), что этот код не выдает никакой ошибки! Я ожидаю, что утверждение потерпит неудачу. Я ожидаю, что код не запускается в основном потоке. В отладчике я вижу, что при создании экземпляра x я нахожусь в своей очереди в потоке 1 (видя метку). Странно, потому что обычно я вижу метку основного потока в потоке 1. Планируется ли моя очередь в главном потоке (поток 1)?

Когда я изменяю sync на async, утверждение не выполняется . Это то, что я ожидаю, что произойдет с sync также. Ниже приводится прикрепленное изображение потоков, когда утверждение не удалось. Я бы ожидал увидеть ту же самую отладочную информацию, когда я использую sync вместо async.

enter image description here

Читая описание sync в источнике Swift, я читаю следующее:

/// As an optimization, `sync(execute:)` invokes the work item on the thread which
/// submitted it, except when the queue is the main queue or
/// a queue targetting it.

Опять же: за исключением случаев, когда очередь является основной очередью

Почему метод sync в очереди фоновой отправки обрабатывает код для запуска в основном потоке, а async - нет? Я могу четко прочитать, что метод синхронизации в очереди не должен выполняться в главном потоке, но почему мой код игнорирует этот сценарий?

Ответы [ 2 ]

2 голосов
/ 06 марта 2019

Я полагаю, вы неправильно прочитали этот комментарий в заголовке. Вопрос не в том, отправляете ли вы из очереди main, а в том, отправляете ли вы в очередь main.

Итак, вот хорошо известная sync оптимизация, при которой отправленный блок будет выполняться в текущем потоке:

let backgroundQueue = DispatchQueue(label: "internalqueue", attributes: .concurrent)

// We'll dispatch from main thread _to_ background queue

func dispatchingToBackgroundQueue() {
    backgroundQueue.sync {
        print(#function, "this sync will run on the current thread, namely the main thread; isMainThread =", Thread.isMainThread)
    }
    backgroundQueue.async {
        print(#function, "but this async will run on the background queue's thread; isMainThread =", Thread.isMainThread)
    }
}

Когда вы используете sync, вы говорите GCD: «Эй, пусть этот поток подождет, пока другой поток не выполнит этот блок кода». Итак, GCD достаточно умен, чтобы понять, «хорошо, если этот поток не будет ничего делать, пока я жду, пока будет запущен блок кода, я мог бы также запустить его здесь, если смогу, и сохранить дорогостоящий контекст». переключиться на другой поток. "

Но в следующем сценарии мы что-то делаем в некоторой фоновой очереди и хотим отправить ее обратно в очередь main. В этом случае GCD не будет выполнять вышеупомянутую оптимизацию, а всегда будет запускать задачу, отправленную в очередь main в очереди main:

// but this time, we'll dispatch from background queue _to_ the main queue

func dispatchingToTheMainQueue() {
    backgroundQueue.async {
        DispatchQueue.main.sync {
            print(#function, "even though it’s sync, this will still run on the main thread; isMainThread =", Thread.isMainThread)
        }
        DispatchQueue.main.async {
            print(#function, "needless to say, this async will run on the main thread; isMainThread =", Thread.isMainThread)
        }
    }
}

Это происходит потому, что в основной очереди должны выполняться определенные вещи (например, обновления пользовательского интерфейса), и если вы отправляете его в основную очередь, он всегда выполнит этот запрос и не будет пытаться выполнить какие-либо действия. оптимизация, чтобы избежать переключения контекста.


Давайте рассмотрим более практический пример последнего сценария.

func performRequest(_ url: URL) {
    URLSession.shared.dataTask(with: url) { data, _, _ in
        DispatchQueue.main.sync {
            // we're guaranteed that this actually will run on the main thread
            // even though we used `sync`
        }
    }
}

Теперь, как правило, мы будем использовать async при отправке обратно в основную очередь, но комментарий в документации заголовка sync просто сообщает нам, что эта задача отправляется обратно в основную очередь с помощью sync. фактически выполняется в основной очереди, а не в фоновой очереди URLSession, как вы могли бы опасаться.

2 голосов
/ 06 марта 2019

Давайте рассмотрим:

/// As an optimization, `sync(execute:)` invokes the work item on the thread which
/// submitted it, except when the queue is the main queue or
/// a queue targetting it.

Вы вызываете sync() в своей очереди. Является ли эта очередь основной или предназначается для основной очереди? Нет, это не так. Таким образом, исключение не имеет значения, и только эта часть:

sync(execute:) вызывает рабочий элемент в потоке, который его отправил

Итак, тот факт, что ваша queue является фоновой очередью, не имеет значения. Блок выполняется потоком, в котором был вызван sync(), который является основным потоком (который вызвал viewDidLoad(), который вызвал dispatchFun()).

...