Я хочу устранить предупреждения о параллелизме, которые я получаю после включения полной проверки параллелизма в Xcode.
Вот простой пример, иллюстрирующий предупреждение. Предположим, у меня есть ContentView, у которого есть длительная задача в фоновом потоке. Когда я вызываю длительную задачу внутри задачи Task {...}
, я получаю предупреждение «Передача аргумента неотправляемого типа ContentView вне основного, изолированного от актера контекста может привести к гонкам данных». Я получаю это предупреждение, когда включаю завершение настроек параллелизма в Xcode.
Я попробовал сделать ContentView отправляемым, но он генерирует дополнительные предупреждения (см. второе изображение ниже) и не будет работать в моей ситуации.
Как мне обойти это? ценю любую помощь/направление
См. код ниже:
struct ContentView: View {
@State private var disable: Bool = false
@State private var value: Double = 0
var body: some View {
VStack {
Button {
disable = true
} label: {
Text("Press")
}
.task {
await value = self.fetchValue() //<---Here is the warning. See image below
}
Text("\(value)")
}
}
}
extension ContentView {
func fetchValue() async -> Double {
//long running task
try? await Task.sleep(nanoseconds: 60)
return 2.0
}
}
Вот предупреждение:
спасибо за отзыв, обновил вопрос, указав область внимания, а также полный код для компиляции и запуска.
нет веской причины для смешивания Observable и ObservableObject. Observable заменяет ObservableObject.
Переместите код внутри задачи в задачу (id: selectedFile).
@loremipsum, может быть, я что-то упускаю... перемещение кода в задачу (id: selectedFile) ничего не изменило! У меня все еще есть то же самое предупреждение. Предупреждение отображается, когда в настройках параллелизма установлено завершение в Xcode.
@JoakimDanielson, создание статического fetchValue() решило одну проблему, но создало другие проблемы. Я получаю предупреждения, показанные на втором изображении выше. Решение Лоремипсума работает
static
в Swift6 будет работать только с типами Sendable
. Обертки свойств SwiftUI не являются Sendable
, поэтому представления никогда не будут Sendable
. ИМХО, в ближайшем будущем вся эта static
штука будет перекачивать тонну кода разработчика. Наконец-то все эти одиночки будут решены! ржу не могу. Хотя люди, скорее всего, просто отметят их MainActor
, засорив ветку.
По сути, проблема в том, что, поскольку ContentView
не изолировано от какого-либо конкретного актера, fetchValue
тоже является nonisolated
. А функции nonisolated
async
выполняются на универсальном исполнителе (см. SE-0338). Изолирование ContentView
от главного действующего лица — один из способов решения этой проблемы. (И отвечая на ваш вопрос: «Я думал, что представление всегда находится в главном потоке», да, это так, но быть «в главном потоке» технически не совсем то же самое, что быть изолированным от главного актера.)
SwiftUI View
не прикреплен к actor
, которым body
украшен @MainActor
, но не к самому View
.
Если у вас есть func
в View
, вы можете украсить View
с помощью @MainActor
так же, как и «ViewModel».
import SwiftUI
@MainActor // Decorate the View
struct AsyncTest: View {
@State private var isRunning: Bool = false
@State private var value: Double = 0
var body: some View {
VStack {
Button {
print("press")
isRunning = true
} label: {
Text("Press")
}.disabled(isRunning)
.task(id: isRunning) {
guard isRunning else {return}
print("start")
value = await fetchValue()
print("end")
isRunning = false
}
if isRunning {
ProgressView()
}
Text("\(value)")
}
}
}
extension AsyncTest {
func fetchValue() async -> Double {
await Task.detached(priority: .userInitiated) {
// Mock long runing task
_ = (0...10000000).map { n in
n.description
}.sorted(using: KeyPathComparator(\.description))
//long running task
//try? await Task.sleep(nanoseconds: 60)
return 2.0
}.value
}
}
#Preview {
AsyncTest()
}
это работает!! Спасибо. Я пытаюсь понять, я думал представление всегда находится в основном потоке и зачем нам его украшать @MainActor?
@RXP Люди так говорят, но это не так, украшен только body
. View
будет работать на MainActor
по наследству (корневой доступ начинается с MA). Просто не факт, что у вас есть возможность вызвать его в фоновом режиме.
@RXP Обратите внимание, что «замыкания», подобные тем, которые были в исходном коде, не блокируются новым параллелизмом, вам все равно придется переместить код Task
в .task
понял... Я думаю, мне нужно переместить код в .task, потому что теперь чтение файла занимает гораздо больше времени и использует тонну памяти. Раньше чтение файла занимало около 40 секунд, а теперь чтение того же файла занимает более 10 минут и тонну памяти. Мне пришлось убить его... Мне нужно продолжить исследование этого...
Да, есть масса способов правильно «отсоединиться» от основного потока, единственное, что там должно быть, — это финальное задание.
если мне нужно использовать .fileImporter(...), как мне использовать его в .task()? есть другая схема? У меня есть .fileImporter(..) и внутри него есть Task {...}
@RXP использует выбранный файл в качестве идентификатора. Посмотрите на переменную isRunning. Следуйте той же схеме.
@RXP, вы также можете увидеть этот другой образец. Это та же предпосылка stackoverflow.com/questions/77080477/…
спасибо @loremipsum. что помогает! Я переместил код в .task(id: selectedFile), как вы предложили, но теперь он запускается навсегда и использует более 1 ГБ памяти. Раньше он использовал всего 30 МБ памяти и читал тот же файл менее чем за 40 секунд. Есть ли какие-нибудь подсказки для меня, чтобы разобраться в этой проблеме?
@Роб, это имеет смысл... позволь мне вынести это в отдельное приложение и опубликовать как новый вопрос. Спасибо
@RXP был выбранный файл URL-адресом или содержимым файла? Это должен быть URL-адрес, чтобы минимизировать занимаемое пространство.
выбранный файл - это URL
Просмотрев еще немного, понял, что при использовании .task() код выполняется в основном потоке. Однако пользовательский интерфейс отзывчив, что объясняет концепцию async/await. Это причина того, что это занимает много времени? Я пытаюсь изолировать код и воспроизвести поведение в отдельной функции, но это немного сложно :-)
@RXP очень вероятен (я думаю, в этом проблема), как я уже говорил выше, есть несколько разных способов безопасно отсоединиться от главного актера, что-то вроде «someValue = await Task.detached {/* некоторый код */}. value» в функции может работать таким образом, только окончательное назначение выполняется в MainActor. Трудно предложить реалистичное решение без полного примера, упомянутого Робом.
@RXP это старо, но в некоторой степени актуально stackoverflow.com/questions/73538764/…
@loremipsum, спасибо! очень интересно. Я осознаю, как многого я не знаю и насколько я неправильно понимал концепции. Позвольте мне прочитать и посмотреть, как подать заявку.
@RXP, посмотрите на новое fetchValue
Я внес небольшое изменение, которое должно решить вашу проблему.
@loremipsum опубликовал новый вопрос с помощью fileImporter: stackoverflow.com/q/78286044/1219836
@RXP, ты пробовал добавленную мной отдельную линию?
@loremipsum да, я только что это сделал, и, похоже, это ничего не меняет!
@loremipsum, Йоаким предложил попробовать запустить Instruments, и, что очень странно, это занимает чуть меньше 40 секунд, а я попытался запустить его на реальном устройстве, и чтение того же файла также занимает менее 40 секунд. Кажется, проблема возникает только в режиме отладки, что очень странно! Что мне следует сделать, чтобы ускорить работу в режиме отладки?
@RXP У меня не было времени посмотреть другой ваш код. Так что я не уверен. Под DEBUG вы имеете в виду предварительный просмотр? DEBUG по определению — это то, что вы меняете в схеме. Устройства также могут иметь проекты отладки. Если это изолировано от превью, я бы предложил просто использовать файл меньшего размера для насмешки.
@loremipsum, под «Отладкой» я имею в виду запуск в симуляторе и на реальном устройстве с включенными символами отладки, а не оптимизированный код. Запуская приложение с помощью инструментов, оно работает очень быстро. Я предполагаю, что Instruments оптимизирует код. я могу ошибаться
Давайте продолжим обсуждение в чате.
@RXP, это кажется странным, ты уверен, что тело не перерисовывается? avanderlee.com/swiftui/debugging-swiftui-views попробуйте использовать «печать изменений»
Это потому, что у вас перепутан порядок await
и value
, попробуйте следующее:
.task {
value = await AsyncController().fetchValue()
}
Также лучше всего изменить extension AsyncTest
на struct AsyncController
или что-то в этом роде, чтобы он не запускался в основном потоке, что происходит с любой асинхронной функцией, объявленной внутри View
, когда вы используете @StateObject
или @ObservedObject
. Важно то, где объявлена функция.
struct AsyncController {
func fetchValue() async -> Double {
//long running task
try? await Task.sleep(nanoseconds: 60)
// safe to do processing here because this will be a background thread
return 2.0
}
}
Еще лучше, сделайте AsyncController
и EnvironmentKey
, чтобы вы могли высмеивать его для предварительного просмотра через .environment
.
struct ContentView: View {
@State private var disable: Bool = false
@State private var value: Double = 0
var body: some View {
VStack {
Button(action: {
disable = true
fetchAndUpdateValue()
}) {
Text("Press")
}
Text("\(value)")
}
}
private func fetchAndUpdateValue() {
Task {
let fetchedValue = await fetchValue()
value = fetchedValue
}
}
}
extension ContentView {
private func fetchValue() async -> Double {
// Simulate long running task
await Task.sleep(nanoseconds: 60)
return 2.0
}
}
Я создал новую частную функцию fetchAndUpdateValue(), которая выполняет асинхронную операцию и соответствующим образом обновляет свойство значения.
Когда кнопка нажата, она устанавливает состояние отключения и вызывает метод fetchAndUpdateValue() для асинхронного получения значения.
Функция fetchValue() остается неизменной, продолжая выполнять асинхронную операцию.
Чтобы сделать этот вопрос более понятным, было бы полезно сжать ваш код до кода списка, необходимого для отображения всей проблемы. Как написано, он не компилируется, поскольку опирается на несколько типов, которые вы не предоставляете. Вы можете предоставить их упрощенные версии, и я думаю, нам будет проще вам помочь.