Создание NSAttributedString в фоновой очереди: [NSCell init] должен использоваться только из основного потока.

У меня есть приложение Cocoa на основе NSDocument, которое представляет текстовые документы пользователю. Содержимое документа считывается в фоновой очереди, что вызывает проблему:

Я использую NSAttributedString с изображениями, т.е. он может содержать NSTextAttachment и NSTextAttachmentCell. Когда я пытаюсь инициализировать вложение для изображения, и у меня активирована основная проверка потока в Xcode, я получаю следующую ошибку:

// On background queue:
let attachment = NSTextAttachment()
attachment.attachmentCell = NSTextAttachmentCell(imageCell: image) <-

"[NSCell init] must be used from main thread only"

Моя первая попытка состояла в том, чтобы обернуть этот код в DispatchQueue.main.sync {}, но это вызывало взаимоблокировку с NSDocument время от времени, когда происходило автосохранение или когда пользователь сохранял документ.

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

Мой вопрос:

Могу ли я игнорировать средство проверки основного потока в Xcode и все равно создавать экземпляр NSTextAttachmentCell в фоновой очереди?

Все, что я делаю в фоновой очереди, — это инициализирую атрибутивную строку с ее вложениями. Дальнейшие модификации производятся в основной очереди.

Последовательность событий

  1. Thread 2 (bg queue): Need to update abc.txt for some reason. Get read/write access to abc.txt via NSFileCoordinator
    • Thread 2 (bg) now in NSFileCoordinator block
  2. Thread 1 (MAIN): User-initiated NSDocument save, NSDocument requests write access to abc.txt via NSFileCoordinator
    • Thread 1 (MAIN) now blocked, waiting for file coordinator lock of Thread 2
  3. Thread 2 (bg queue): Moving along in file coordinator block..., trying to initialize NSAttributedString, oh, it contains an attachment, can't initialize NSTextAttachmentCell on background queue, let me hand this off to the MAIN queue real' quick... ⚡️ DEADLOCK ⚡️
    • Thread 2 (bg) is now waiting for Thread 1 (MAIN), which is waiting in front of its file coordinator access block for Thread 2 (bg) to finish with its file coordinator block.

person Mark    schedule 04.06.2020    source источник
comment
Я не эксперт, но может ли документ писать в фоновой очереди?   -  person Willeke    schedule 13.06.2020


Ответы (1)


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

person Lucas Derraugh    schedule 05.06.2020
comment
Спасибо за ссылку. Мне удалось создать взаимоблокировку даже с main.async при использовании координации файлов: (1) моя фоновая операция получает блокировку чтения/записи в файле с помощью координации файлов и начинает читать файл, (2) NSDocument начинает автосохранение также используя координацию файлов и блокируя основную очередь, (3) в моей фоновой очереди мой код пытается создать экземпляр NSAttributedString с ячейкой вложения, выгружая ее в (заблокированную) основную очередь. Не уверен, как это решить... - person Mark; 08.06.2020
comment
Блокирует ли автосохранение NSDocument основную очередь навсегда? №3 звучит не относящимся к проблеме. Возможно, это случай попытки избежать функции автосохранения: stackoverflow.com/questions/44161308/ - person Lucas Derraugh; 08.06.2020
comment
Да, сохранение NSDocument вызывает взаимоблокировку с моей фоновой очередью и, следовательно, навсегда блокирует основную очередь. Отключение автосохранения, к сожалению, не вариант. Я добавил несколько Thread.sleep() в нужных местах, чтобы воспроизвести это надежно, и заметил, что пользовательское сохранение с помощью Cmd-S вызывает ту же проблему. Основная проблема заключается в том, что и NSDocument, и моя фоновая операция используют NSFileCoordinator для получения доступа для чтения/записи к файлу, а затем фоновая операция вынуждена обращаться к основной очереди (заблокированной NSDocument), чтобы инициировать NSTextAttachmentCell. - person Mark; 09.06.2020
comment
Я добавил к вопросу точную последовательность событий. - person Mark; 09.06.2020