В Swift Concurrency мы можем использовать Task.sleep()
и Task.yield()
, чтобы перевести конкретную задачу в режим ожидания или приостановки в течение определенного периода времени. Оба они выглядят и ведут себя одинаково, но есть несколько существенных различий, о которых следует знать Swift-разработчикам.
Зная эти различия, вы сможете лучше понять, когда и как использовать те или иные функции, а также оптимизировать свой код соответствующим образом. Прежде чем перейти к рассмотрению различий, давайте сначала объясним, что делают эти модификаторы.
Как использовать Task.sleep()
С помощью Task.sleep()
вы можете приостановить выполнение задачи на заданный срок:
try await Task.sleep(for: .seconds(5))
В отличие от обычных методов сна, знакомых нам по неконкурентному коду, этот сон не блокирует основной поток. Это означает, что он позволяет работать ожидающим задачам с более низким приоритетом.
Если задача будет отменена до окончания сна, эта функция выбросит ошибку CancellationError
, которая известна нам по обычной отмене задач.
Когда следует использовать Task.sleep()?
Вы должны использовать сон, когда хотите ввести задержку в асинхронном контексте. Распространенный пример — отложить ввод пользователя и дождаться паузы в наборе текста перед выполнением поискового запроса:
func search(_ query: String) { /// Cancel any previous searches that might be 'sleeping'. currentSearchTask?.cancel() let newSearchTask = Task { do { /// Sleep for 0.5 seconds to wait for a pause in typing before executing the search. try await Task.sleep(for: .milliseconds(500)) print("Starting to search!") /// A simplified static result and search implementation. searchResults = Self.articleTitlesDatabase .filter { $0.lowercased().contains(query.lowercased()) } } catch { print("Search was cancelled!") } } self.currentSearchTask = newSearchTask }
Другими распространенными случаями использования являются:
- Опрос API через определенные промежутки времени в сочетании с асинхронными последовательностями
- Ограничение скорости исходящих сетевых запросов
- Искусственные задержки для тестирования пользовательского интерфейса
Обязательно учитывайте отмену при использовании Task.sleep()
, соблюдая функциональность throw.
Как использовать Task.yield()
Метод Task.yield()
приостанавливает выполнение текущей задачи и позволяет выполнить другие задачи:
await Task.yield()
Вы можете использовать метод для приостановки задачи, возможно, во время длительного выполнения. Это позволит другим задачам некоторое время поработать, прежде чем выполнение вернется к этой задаче.
Однако обратите внимание: если текущая задача уже является самой приоритетной в системе, исполнитель немедленно возобновит выполнение этой же задачи. Другими словами, метод Task.yield()
может не оказать никакого влияния.
Когда следует использовать Task.yield()?
По моему опыту, существует не так много распространенных сценариев, в которых вам понадобится использовать Task.yield()
. В большинстве случаев лучше использовать Task.sleep()
. Для меня самым распространенным случаем было написание тестов для асинхронного кода. Я большой поклонник репозитория Swift Concurrency Extras, который демонстрирует использование следующим образом:
func testIsLoading() async { /// Run on a serial executor to attempt running all tasks serially. await withMainSerialExecutor { let model = NumberFactModel(getFact: { /// Yield the current task and let other tasks continue first. await Task.yield() return "\($0) is a good number." }) let task = Task { await model.getFactButtonTapped() } /// Yield the test so the `getFactButtonTapped()` method gets called. await Task.yield() XCTAssertEqual(model.isLoading, true) XCTAssertEqual(model.fact, nil) /// Wait for the task to return its value. await task.value XCTAssertEqual(model.isLoading, false) XCTAssertEqual(model.fact, "0 is a good number.") } }
Использование Task.yield()
позволяет тестируемому коду выполнять свою работу и гарантировать, что результаты будут постоянно доступны для проверки. Если у вас есть другой пример, я буду рад узнать!
Различия между Task.sleep() и Task.yield()
Оба метода приостанавливают выполнение, но у них есть несколько ключевых различий.
Метод Task.sleep()
приостанавливает выполнение на определенное время, в то время как метод Task.yield()
может приостановить выполнение только в том случае, если другие задачи с аналогичным или более низким приоритетом ожидают выполнения. Поэтому длительность приостановки фиксирована только для Task.sleep()
и неопределенна для Task.yield()
.
Метод сна можно прервать через отмену, в то время как Task.yield()
только передает управление. Оба метода являются неблокирующими для соответствующих потоков.
Заключение
И Task.sleep()
, и Task.yield()
приостанавливают выполнение, но делают это по-разному. В большинстве случаев вы будете использовать метод sleep
, в то время как метод yield
может быть полезен при написании тестов для асинхронных методов.
Если вы хотите узнать больше о Swift Concurrency, обязательно ознакомьтесь с курсом или прочитайте другие статьи о Swift Concurrency.
Спасибо!