Как я могу гарантировать, что мой DispatchQueue выполняет некоторый код в основном потоке? - PullRequest
0 голосов
/ 06 января 2020

У меня есть синглтон, который управляет массивом. Этот синглтон может быть доступен из нескольких потоков, поэтому он имеет собственный внутренний DispatchQueue для управления доступом для чтения / записи между потоками. Для простоты скажем, что это последовательная очередь.

Наступает время, когда синглтон будет считывать данные из массива и обновлять пользовательский интерфейс. Как мне справиться с этим?

Какой поток моей внутренней очереди отправки не известен, верно? Это просто деталь реализации, о которой мне не стоит беспокоиться? В большинстве случаев это кажется нормальным, но в этой специфической c функции мне нужно быть уверенным, что она использует основной поток.

Можно ли что-то делать в соответствии с:

myDispatchQueue.sync { // Synchronize with internal queue to ensure no writes/reads happen at the same time
    DispatchQueue.main.async { // Ensure that it's executed on the main thread
        for item in internalArray {
            // Pretend internalArray is an array of strings
            someLabel.text = item
        }
    }
}

Итак, мои вопросы:

  1. Это нормально? Кажется странным / неправильным вкладывать очереди отправки. Есть ли способ лучше? Может быть, что-то вроде myDispatchQueue.sync(forceMainThread: true) { ... }?
  2. Если я НЕ использовал DispatchQueue.main.async { ... }, и я вызвал функцию из основного потока, могу ли я быть уверен, что моя внутренняя очередь отправки выполнит ее на том же (основном) нить как это называется? Или это также «деталь реализации», где она может быть, но она также может вызываться в фоновом потоке?

По сути, я запутался, что потоки кажутся деталями реализации, которыми вы не являетесь. должен беспокоиться об очередях, но что случается по случайному случаю, когда вам НУЖНО беспокоиться?

Простой пример кода:

class LabelUpdater {
    static let shared = LabelUpdater()

    var strings: [String] = []
    private let dispatchQueue: dispatchQueue

    private init {
        dispatchQueue = DispatchQueue(label: "com.sample.me.LabelUpdaterQueue")
        super.init()
    }

    func add(string: String) {
        dispatchQueue.sync {
            strings.append(string)
        }
    }

    // Assume for sake of example that `labels` is always same array length as `strings`
    func updateLabels(_ labels: [UILabel]) {
        // Execute in the queue so that no read/write can occur at the same time.
        dispatchQueue.sync {
            // How do I know this will be on the main thread? Can I ensure it?
            for (index, label) in labels.enumerated() {
                label.text = strings[index]
            }
        }
    }
}

Ответы [ 2 ]

1 голос
/ 06 января 2020

Да, вы можете вкладывать отправку в одну очередь внутри отправки в другую очередь. Мы часто так делаем.

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

enter image description here

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

Например, вы можете хочу сделать следующее:

func process(completion: @escaping (String) -> Void) {
    syncQueue.sync {
        let result = ...            // note, this runs on thread associated with `syncQueue` ...

        DispatchQueue.main.async {
            completion(result)      // ... but this runs on the main thread
        }
    }
}

Это гарантирует, что основная очередь не взаимодействует с какими-либо внутренними свойствами этого класса, а просто result, созданный в этом замыкании, передается syncQueue .


Заметьте, все это не связано с тем, что он является одиночным. Но так как вы подняли topi c, я бы посоветовал не использовать синглтоны для данных модели. Это нормально для приемников, контроллеров без сохранения состояния и т. П., Но обычно не рекомендуется для данных модели.

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

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

0 голосов
/ 26 января 2020

Попробуйте использовать OperationQueues (Операции) , поскольку они имеют состояния:

  • isReady : он готов к запуску
  • isExecuting : задача в настоящий момент выполняется
  • isFinished : после завершения процесса
  • isCancelled : задача отменена

Преимущества очередей операций:

  • Определение порядка выполнения
  • наблюдение их состояний
  • Отмена операций

Операции могут быть приостановлены, возобновлены и отменены. Как только вы отправите задачу с помощью Grand Central Dispatch, у вас больше не будет контроля или понимания выполнения этой задачи. API NSOperation является более гибким в этом отношении, предоставляя разработчику контроль над жизненным циклом операции

https://developer.apple.com/documentation/foundation/operationqueue

https://medium.com/@aliakhtar_16369 / параллелизм-в-Свифт-операции, а-операция-очередь неполные 3-a108fbe27d61

...