AsyncStream не выполняет закрытие

поэтому я читаю книгу Modern Concurrency с raywenderlich.com и предполагаю, что книга устарела или что-то в этом роде, я пытаюсь запустить closure внутри AsyncStream, но, похоже, это не получается, я все еще хорош новичок в этом Async/Await, но при добавлении некоторых точек останова я вижу, что мой код туда не попадает. Это мой код и скриншот с некоторыми предупреждениями. Я не совсем знаком с тем, что означают предупреждения, просто пытаюсь изучить все эти новые вещи, я был бы очень признателен за помощь, и есть ли способ исправить это с помощью Swift 6? Заранее спасибо!

Ссылка на захваченную переменную «обратный отсчет» в параллельно выполняющемся коде; это ошибка в Swift 6

Мутация захваченной var 'countdown' в параллельно выполняющемся коде; это ошибка в Swift 6

func countdown(to message: String) async throws {
    guard !message.isEmpty else { return }
    var countdown = 3

    let counter = AsyncStream<String> { continuation in
      Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
          continuation.yield("\(countdown)...")
          countdown -= 1
        }
    }

    for await countDownMessage in counter {
      try await say(countDownMessage)
    }
 }

Новая версия книги будет доступна в ближайшее время, это одна из проблем, которые были решены.

jrturton 02.02.2023 18:25
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
1
74
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

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

Timer.scheduleTimer требует, чтобы он был запланирован в цикле выполнения. С практической точки зрения это означает, что мы хотели бы запланировать его в цикле выполнения основного потока. Итак, вы либо вызываете scheduleTimer из основного потока, либо создаете Timer и вручную добавляете(_:forMode:) его в RunLoop.main . См. раздел «Планирование таймеров в циклах выполнения» документации Timer.

Проще всего было бы просто возложить эту функцию на главного актера. Например.,

@MainActor
func countdown(to message: String) async throws { … }

Здесь также есть несколько других проблем:

  1. Я бы предложил определить переменную countdown внутри AsyncStream:

    @MainActor
    func countdown(to message: String) async throws {
        guard !message.isEmpty else { return }
    
        let counter = AsyncStream<String> { continuation in
            var countdown = 3
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
                continuation.yield("\(countdown)...")
                countdown -= 1
            }
        }
    
        for await countDownMessage in counter {
            try await say(countDownMessage)
        }
    }
    
  2. AsyncStream никогда не заканчивается. Возможно, вы захотите закончить его, когда он достигнет нуля:

    @MainActor
    func countdown(to message: String) async throws {
        guard !message.isEmpty else { return }
    
        let counter = AsyncStream<String> { continuation in
            var countdown = 3
            Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
                continuation.yield("\(countdown)...")
    
                // presumably you want this countdown timer to finish when it hits zero
    
                guard countdown > 0 else {
                    timer.invalidate()
                    continuation.finish()
                    return
                }
    
                // otherwise, decrement and carry on
    
                countdown -= 1
            }
        }
    
        for await countDownMessage in counter {
            try await say(countDownMessage)
        }
    }
    
  3. Должно быть замыкание continuation.onTermination для обработки отмены асинхронной последовательности.

    @MainActor
    func countdown(to message: String) async throws {
        guard !message.isEmpty else { return }
    
        let counter = AsyncStream<String> { continuation in
            var countdown = 3
            let timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { timer in
                continuation.yield("\(countdown)...")
    
                // presumably you want this countdown timer to finish when it hits zero
    
                guard countdown > 0 else {
                    timer.invalidate()
                    continuation.finish()
                    return
                }
    
                // otherwise, decrement and carry on
    
                countdown -= 1
            }
    
            continuation.onTermination = { _ in
                timer.invalidate()
            }
        }
    
        for await countDownMessage in counter {
            try await say(countDownMessage)
        }
    }
    

Возвращаясь к первоначальному вопросу (почему это не работает), лично я бы вообще избегал использования Timer в сочетании с параллелизмом Swift. Таймер GCD был бы лучше, так как он не требует RunLoop. Даже лучше, я бы посоветовал Task.sleep. Излишне говорить, что это предназначено для работы с параллелизмом Swift, а также может быть отменено.

Я бы лично предложил что-то вроде:

func countdown(to message: String) async throws {
    guard !message.isEmpty else { return }
    
    let counter = AsyncStream<String> { continuation in
        let task = Task {
            for countdown in (0...3).reversed() {
                try await Task.sleep(for: .seconds(1))
                continuation.yield("\(countdown)...")
            }
            
            continuation.finish()
        }
        
        continuation.onTermination = { _ in
            task.cancel()
        }
    }
    
    for await countDownMessage in counter {
        try await say(countDownMessage)
    }
}

Task.sleep — это метод, используемый в обновленной версии книги.

jrturton 02.02.2023 18:27

О, парень! Большое спасибо, ваш ответ был действительно полезен, вы дали мне весь контекст, необходимый для понимания того, что происходит и как это решить. Большое спасибо!!

Fernando Ivan Perez Ruiz 05.02.2023 04:39

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