Spec-Zone.ru › Tokio

Модуль task

Доступен только при включённой функции крейта rt.

Асинхронные зелёные потоки.

Что такое задачи?

Задача — это лёгкая, неблокирующая единица выполнения. Задача похожа на поток ОС, но вместо управления планировщиком ОС задачами управляет среда выполнения Tokio. Другое название этого общего подхода — зелёные потоки. Если вы знакомы с гороутинами Go, корутинами Kotlin или процессами Erlang, можете считать задачи Tokio похожими на них.

Основные сведения о задачах:

  • Задачи лёгкие. Поскольку задачи планирует среда выполнения Tokio, а не операционная система, создание задач и переключение между ними не требуют переключения контекста и имеют довольно низкие накладные расходы. Создание, выполнение и уничтожение большого количества задач обходится недорого, особенно по сравнению с потоками ОС.

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

  • Задачи неблокирующие. Обычно, когда поток ОС выполняет ввод-вывод или должен синхронизироваться с другим потоком, он блокируется, позволяя ОС запланировать другой поток. Когда задача не может продолжить выполнение, она должна уступить управление, чтобы среда выполнения Tokio могла запланировать другую задачу. Как правило, задачам не следует выполнять системные вызовы и другие операции, которые могут блокировать поток, поскольку это не позволит выполняться и другим задачам в том же потоке. Вместо этого этот модуль предоставляет API для выполнения блокирующих операций в асинхронном контексте.

Работа с задачами

Этот модуль предоставляет следующие API для работы с задачами:

Запуск задач

Пожалуй, важнейшая функция этого модуля — task::spawn. Эту функцию можно считать асинхронным аналогом thread::spawn из стандартной библиотеки. Она принимает блок async или другой future и создаёт новую задачу для параллельного выполнения этой работы:

use tokio::task;

task::spawn(async {
    // perform some work here...
});

Как и std::thread::spawn, task::spawn возвращает структуру JoinHandle. JoinHandle сама является future, который можно использовать, чтобы дождаться результата запущенной задачи. Например:

use tokio::task;

let join = task::spawn(async {
    // ...
    "hello world!"
});

// ...

// Await the result of the spawned task.
let result = join.await?;
assert_eq!(result, "hello world!");

Также, как и в случае с типом JoinHandle из std::thread, если запущенная задача вызовет панику, ожидание её JoinHandle вернёт JoinError. Например:

use tokio::task;

let join = task::spawn(async {
    panic!("something bad happened!")
});

// The returned result indicates that the task failed.
assert!(join.await.is_err());

spawn, JoinHandle и JoinError доступны при включённом флаге функции «rt».

Отмена

Запущенные задачи можно отменить с помощью методов JoinHandle::abort или AbortHandle::abort. При вызове одного из этих методов задаче отправляется сигнал о необходимости остановиться при следующей уступке управления в точке .await. Если задача уже простаивает, она будет остановлена как можно скорее и больше не запустится до остановки. Кроме того, завершение работы среды выполнения Tokio (например, при возврате из #[tokio::main]) немедленно отменяет все её задачи.

При остановке задачи её выполнение прекратится в той точке .await, где она уступила управление. Все локальные переменные уничтожаются вызовом их деструкторов. После завершения остановки ожидание JoinHandle завершится ошибкой отмены.

Обратите внимание: отмена задачи не гарантирует, что она завершится ошибкой отмены, поскольку задача может сначала завершиться обычным образом. Например, если задача ни разу не уступит управление среде выполнения между вызовом abort и завершением задачи, JoinHandle вместо этого сообщит, что задача завершилась обычным образом.

Имейте в виду, что задачи, запущенные с помощью spawn_blocking, нельзя отменить, поскольку они не являются асинхронными. Если вызвать abort для задачи spawn_blocking, это не даст никакого эффекта, и задача продолжит выполняться как обычно. Исключение — задача ещё не начала выполняться; в этом случае вызов abort может помешать её запуску.

Имейте в виду, что вызовы JoinHandle::abort лишь планируют отмену задачи и возвращают управление до завершения отмены. Чтобы дождаться завершения отмены, дождитесь завершения задачи, выполнив ожидание JoinHandle. Аналогично, метод JoinHandle::is_finished возвращает true только после завершения отмены.

Многократный вызов JoinHandle::abort даёт тот же результат, что и однократный.

Tokio также предоставляет AbortHandle, который похож на JoinHandle, но не позволяет дождаться завершения задачи. У каждой задачи может быть только один JoinHandle, но несколько AbortHandle.

Блокировка и уступка управления

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

Вместо этого Tokio предоставляет два API для выполнения блокирующих операций в асинхронном контексте: task::spawn_blocking и task::block_in_place.

Обратите внимание: если вызвать неасинхронный метод из асинхронного кода, этот метод всё равно будет выполняться в асинхронном контексте, поэтому и там следует избегать блокирующих операций. Это относится и к деструкторам объектов, уничтожаемых в асинхронном коде.

spawn_blocking

Функция task::spawn_blocking похожа на функцию task::spawn, рассмотренную в предыдущем разделе, но вместо запуска неблокирующего future в среде выполнения Tokio она запускает блокирующую функцию в выделенном пуле потоков для блокирующих задач. Например:

use tokio::task;

task::spawn_blocking(|| {
    // do some compute-heavy work or call synchronous code
});

Как и task::spawn, task::spawn_blocking возвращает JoinHandle, который можно использовать, чтобы дождаться результата блокирующей операции:

let join = task::spawn_blocking(|| {
    // do some compute-heavy work or call synchronous code
    "blocking completed"
});

let result = join.await?;
assert_eq!(result, "blocking completed");
block_in_place

При использовании многопоточной среды выполнения также доступна функция task::block_in_place. Как и task::spawn_blocking, эта функция позволяет выполнять блокирующую операцию из асинхронного контекста. Однако, в отличие от spawn_blocking, block_in_place переводит текущий рабочий поток в режим блокирующего потока, перемещая другие задачи, выполняющиеся в этом потоке, в другой рабочий поток. Это может повысить производительность за счёт отсутствия переключений контекста.

Например:

use tokio::task;

let result = task::block_in_place(|| {
    // do some compute-heavy work or call synchronous code
    "blocking completed"
});

assert_eq!(result, "blocking completed");
yield_now

Кроме того, этот модуль предоставляет асинхронную функцию task::yield_now, аналогичную функции thread::yield_now из стандартной библиотеки. Вызов этой функции, а затем её awaiting приведёт к тому, что текущая задача уступит управление планировщику среды выполнения Tokio, позволяя запланировать другие задачи. Со временем планировщик снова начнёт опрашивать уступившую задачу, и она сможет продолжить выполнение. Например:

use tokio::task;

async {
    task::spawn(async {
        // ...
        println!("spawned task done!")
    });

    // Yield, allowing the newly-spawned task to execute first.
    task::yield_now().await;
    println!("main task done!");
}

Модули

coop
Утилиты для улучшения кооперативного планирования.
futures
Future, связанные с задачами.
join_set
Коллекция задач, запущенных в среде выполнения Tokio.

Структуры

AbortHandle
Владеющее разрешение отменить запущенную задачу, не дожидаясь её завершения.
Buildertokio_unstable и tracing
Фабрика для настройки свойств новой задачи.
Id
Непрозрачный идентификатор, уникально определяющий задачу среди всех остальных выполняющихся в данный момент задач.
JoinError
Не удалось выполнить задачу до завершения.
JoinHandle
Владеющее разрешение присоединиться к задаче (дождаться её завершения).
JoinSet
Коллекция задач, запущенных в среде выполнения Tokio.
LocalEnterGuard
Защитный объект контекста для LocalSet
LocalKey
Ключ для данных, локальных для задачи.
LocalSet
Набор задач, выполняемых в одном потоке.

Функции

block_in_placert-multi-thread
Выполняет предоставленную блокирующую функцию в текущем потоке, не блокируя исполнитель.
id
Возвращает Id текущей выполняющейся задачи.
spawn
Запускает новую асинхронную задачу и возвращает для неё JoinHandle.
spawn_blocking
Выполняет предоставленное замыкание в потоке, где блокировка допустима.
spawn_local
Запускает future !Send в текущем LocalSet или LocalRuntime.
try_id
Возвращает Id текущей выполняющейся задачи или None, если вызвана вне задачи.
yield_now
Возвращает управление среде выполнения Tokio.

MIT License
Copyright © Tokio Contributors
https://docs.rs/tokio/1.53.1/tokio/task/index.html

Spec-Zone.ru

Настройки Оффлайн Что нового Помощь О нас
Spec-Zone .ru
спецификации, руководства, описания, API