Полная проверка параллелизма включена и способы устранения предупреждений

Я хочу устранить предупреждения о параллелизме, которые я получаю после включения полной проверки параллелизма в 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
    }
}

Вот предупреждение:

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

Rob Napier 06.04.2024 01:56

спасибо за отзыв, обновил вопрос, указав область внимания, а также полный код для компиляции и запуска.

RXP 06.04.2024 02:20

нет веской причины для смешивания Observable и ObservableObject. Observable заменяет ObservableObject.

lorem ipsum 06.04.2024 03:28

Переместите код внутри задачи в задачу (id: selectedFile).

lorem ipsum 06.04.2024 03:30

@loremipsum, может быть, я что-то упускаю... перемещение кода в задачу (id: selectedFile) ничего не изменило! У меня все еще есть то же самое предупреждение. Предупреждение отображается, когда в настройках параллелизма установлено завершение в Xcode.

RXP 06.04.2024 06:28

@JoakimDanielson, создание статического fetchValue() решило одну проблему, но создало другие проблемы. Я получаю предупреждения, показанные на втором изображении выше. Решение Лоремипсума работает

RXP 06.04.2024 14:20
static в Swift6 будет работать только с типами Sendable. Обертки свойств SwiftUI не являются Sendable, поэтому представления никогда не будут Sendable. ИМХО, в ближайшем будущем вся эта static штука будет перекачивать тонну кода разработчика. Наконец-то все эти одиночки будут решены! ржу не могу. Хотя люди, скорее всего, просто отметят их MainActor, засорив ветку.
lorem ipsum 06.04.2024 14:29

По сути, проблема в том, что, поскольку ContentView не изолировано от какого-либо конкретного актера, fetchValue тоже является nonisolated. А функции nonisolatedasync выполняются на универсальном исполнителе (см. SE-0338). Изолирование ContentView от главного действующего лица — один из способов решения этой проблемы. (И отвечая на ваш вопрос: «Я думал, что представление всегда находится в главном потоке», да, это так, но быть «в главном потоке» технически не совсем то же самое, что быть изолированным от главного актера.)

Rob 06.04.2024 18:20
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
3
8
917
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Ответ принят как подходящий

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 06.04.2024 14:19

@RXP Люди так говорят, но это не так, украшен только body. View будет работать на MainActor по наследству (корневой доступ начинается с MA). Просто не факт, что у вас есть возможность вызвать его в фоновом режиме.

lorem ipsum 06.04.2024 14:21

@RXP Обратите внимание, что «замыкания», подобные тем, которые были в исходном коде, не блокируются новым параллелизмом, вам все равно придется переместить код Task в .task

lorem ipsum 06.04.2024 14:23

понял... Я думаю, мне нужно переместить код в .task, потому что теперь чтение файла занимает гораздо больше времени и использует тонну памяти. Раньше чтение файла занимало около 40 секунд, а теперь чтение того же файла занимает более 10 минут и тонну памяти. Мне пришлось убить его... Мне нужно продолжить исследование этого...

RXP 06.04.2024 14:37

Да, есть масса способов правильно «отсоединиться» от основного потока, единственное, что там должно быть, — это финальное задание.

lorem ipsum 06.04.2024 14:41

если мне нужно использовать .fileImporter(...), как мне использовать его в .task()? есть другая схема? У меня есть .fileImporter(..) и внутри него есть Task {...}

RXP 06.04.2024 15:01

@RXP использует выбранный файл в качестве идентификатора. Посмотрите на переменную isRunning. Следуйте той же схеме.

lorem ipsum 06.04.2024 15:17

@RXP, вы также можете увидеть этот другой образец. Это та же предпосылка stackoverflow.com/questions/77080477/…

lorem ipsum 06.04.2024 15:28

спасибо @loremipsum. что помогает! Я переместил код в .task(id: selectedFile), как вы предложили, но теперь он запускается навсегда и использует более 1 ГБ памяти. Раньше он использовал всего 30 МБ памяти и читал тот же файл менее чем за 40 секунд. Есть ли какие-нибудь подсказки для меня, чтобы разобраться в этой проблеме?

RXP 06.04.2024 17:29

@Роб, это имеет смысл... позволь мне вынести это в отдельное приложение и опубликовать как новый вопрос. Спасибо

RXP 06.04.2024 18:11

@RXP был выбранный файл URL-адресом или содержимым файла? Это должен быть URL-адрес, чтобы минимизировать занимаемое пространство.

lorem ipsum 06.04.2024 19:34

выбранный файл - это URL

RXP 06.04.2024 20:16

Просмотрев еще немного, понял, что при использовании .task() код выполняется в основном потоке. Однако пользовательский интерфейс отзывчив, что объясняет концепцию async/await. Это причина того, что это занимает много времени? Я пытаюсь изолировать код и воспроизвести поведение в отдельной функции, но это немного сложно :-)

RXP 06.04.2024 22:32

@RXP очень вероятен (я думаю, в этом проблема), как я уже говорил выше, есть несколько разных способов безопасно отсоединиться от главного актера, что-то вроде «someValue = await Task.detached {/* некоторый код */}. value» в функции может работать таким образом, только окончательное назначение выполняется в MainActor. Трудно предложить реалистичное решение без полного примера, упомянутого Робом.

lorem ipsum 06.04.2024 22:59

@RXP это старо, но в некоторой степени актуально stackoverflow.com/questions/73538764/…

lorem ipsum 06.04.2024 23:01

@loremipsum, спасибо! очень интересно. Я осознаю, как многого я не знаю и насколько я неправильно понимал концепции. Позвольте мне прочитать и посмотреть, как подать заявку.

RXP 06.04.2024 23:25

@RXP, посмотрите на новое fetchValue Я внес небольшое изменение, которое должно решить вашу проблему.

lorem ipsum 06.04.2024 23:31

@loremipsum опубликовал новый вопрос с помощью fileImporter: stackoverflow.com/q/78286044/1219836

RXP 07.04.2024 00:07

@RXP, ты пробовал добавленную мной отдельную линию?

lorem ipsum 07.04.2024 00:10

@loremipsum да, я только что это сделал, и, похоже, это ничего не меняет!

RXP 07.04.2024 00:17

@loremipsum, Йоаким предложил попробовать запустить Instruments, и, что очень странно, это занимает чуть меньше 40 секунд, а я попытался запустить его на реальном устройстве, и чтение того же файла также занимает менее 40 секунд. Кажется, проблема возникает только в режиме отладки, что очень странно! Что мне следует сделать, чтобы ускорить работу в режиме отладки?

RXP 07.04.2024 14:48

@RXP У меня не было времени посмотреть другой ваш код. Так что я не уверен. Под DEBUG вы имеете в виду предварительный просмотр? DEBUG по определению — это то, что вы меняете в схеме. Устройства также могут иметь проекты отладки. Если это изолировано от превью, я бы предложил просто использовать файл меньшего размера для насмешки.

lorem ipsum 07.04.2024 15:02

@loremipsum, под «Отладкой» я имею в виду запуск в симуляторе и на реальном устройстве с включенными символами отладки, а не оптимизированный код. Запуская приложение с помощью инструментов, оно работает очень быстро. Я предполагаю, что Instruments оптимизирует код. я могу ошибаться

RXP 07.04.2024 15:05

Давайте продолжим обсуждение в чате.

lorem ipsum 07.04.2024 15:19

@RXP, это кажется странным, ты уверен, что тело не перерисовывается? avanderlee.com/swiftui/debugging-swiftui-views попробуйте использовать «печать изменений»

lorem ipsum 07.04.2024 15:20

Это потому, что у вас перепутан порядок 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() остается неизменной, продолжая выполнять асинхронную операцию.

Другие вопросы по теме