Dart — это мощный язык программирования, который часто используется для создания мобильных, веб- и настольных приложений. Одной из ключевых особенностей Dart является его поддержка потоков, которые являются мощным инструментом для работы с асинхронными данными.
Потоки в Dart похожи на потоки в других языках программирования, таких как JavaScript и Python. Они позволяют разработчикам работать с данными, поступающими асинхронно, такими как пользовательский ввод, сетевые запросы или данные с датчиков. В этой статье мы рассмотрим потоки в Dart и то, как их можно использовать для создания более отзывчивых и эффективных приложений.
Что такое потоки
В программировании поток — это последовательность инструкций, которая может выполняться независимо от основного потока программы.
Проще говоря, когда программа запущена, она выполняет набор инструкций в определенном порядке. Когда вводится поток, программа теперь может выполнять несколько наборов инструкций одновременно, обеспечивая параллельную обработку.
Потоки позволяют программам выполнять несколько задач одновременно, таких как загрузка данных при отображении индикатора выполнения или воспроизведение музыки во время просмотра веб-страниц. Каждый поток может иметь свой собственный набор инструкций и выполняться независимо, что может повысить производительность программы и быстродействие.
Вот пример потока, где программа обрабатывает одновременно ввод данных с клавиатуру, чтение и запись в файл, а также вывод данных на монитор.
Поскольку потоки могут выполняться одновременно, они также могут создавать проблемы, если они не синхронизированы должным образом. Например, если два потока одновременно обращаются к одному и тому же фрагменту данных, они могут в конечном итоге перезаписать изменения друг друга.
В целом, потоки являются мощным инструментом для повышения производительности программы, но они требуют тщательного управления, чтобы гарантировать, что они не вызывают проблем.
Потоки в Dart
Потоки в Dart реализованы с использованием классов Stream и StreamController. Класс Stream представляет последовательность элементов данных, которые поступают асинхронно с течением времени. Класс StreamController используется для создания нового потока и добавления в него данных.
Когда создается поток, он не содержит никаких данных. Вместо этого данные добавляются в поток с помощью класса StreamController. Как только данные добавлены в поток, их можно прочитать и обработать с помощью класса Stream.
StreamConrtoller
Класс StreamController в Dart предоставляет способ создавать потоки данных и манипулировать ими, позволяя разработчикам контролировать поток данных через свои программы.
Класс StreamController используется для создания нового потока и управления данными, которые отправляются в этот поток. Он предоставляет методы для добавления данных в поток, прослушивания данных из потока и управления поведением потока.
Некоторые из ключевых функций класса StreamController:
- Создание нового потока: Позволяет разработчикам создавать новый поток данных, который может быть использован в их программах.
- Добавление данных в поток: Предоставляет метод для добавления данных в поток, позволяя разработчикам отправлять данные через поток по мере необходимости.
- Прослушивание данных из потока: Предоставляет способ прослушивания данных, отправляемых через поток, позволяя разработчикам обрабатывать эти данные по мере необходимости.
- Настройка поведения потока: Предоставляет методы для управления поведением потока, такие как приостановка или возобновление потока или перевод потока в широковещательный режим.
- Обработка ошибок: Предоставляет способ обработки ошибок, возникающих при обработке данных в потоке, таких как сетевые ошибки или неверный ввод.
Вот некоторые из основных методов Stream и их краткое описание:
- add(data) — Добавить данные в поток.
- addStream(stream) — добавляет все данные из потока в поток, управляемый этим контроллером потока.
- stream — Возвращает поток, управляемый этим контроллером потока.
- close() — Закрыть поток, управляемый этим контроллером потока.
- isClosed — возвращает значение true, если поток, управляемый этим контроллером потока, закрыт.
- isPaused — возвращает значение true, если поток, управляемый этим контроллером потока, приостановлен.
- onListen() — вызывается, когда в поток добавляется первый прослушиватель.
- onPause() — вызывается, когда поток приостановлен.
- onResume() — вызывается при возобновлении потока.
- onCancel() — вызывается, когда последний прослушиватель удаляется из потока.
Stream
Класс Stream является основным компонентов Dart для асинхронного программирования. Он представляет собой последовательность событий, которые происходят с течением времени, таких как пользовательский ввод, сетевые запросы или данные от датчиков. Класс Stream предоставляет способ обрабатывать эти данные по мере их поступления, а не ждать, пока они станут доступны все сразу.
Некоторые из ключевых функций класса Stream:
- Асинхронная обработка: Класс Stream позволяет обрабатывать данные асинхронно, что означает, что программа может продолжать выполняться в ожидании поступления данных.
- Отложенная загрузка: Класс Stream не загружает все данные сразу, а вместо этого загружает их по требованию, как это запрашивает программа.
- Обработка ошибок: Класс Stream предоставляет способ обработки ошибок, возникающих во время обработки данных, таких как сетевые ошибки или неверный ввод.
- Несколько подписчиков: На класс Stream могут быть подписаны несколько подписчиков, что позволяет совместно использовать данные между различными частями программы.
- Настройка: Класс Stream можно настроить с помощью различных операций, таких как фильтрация, сопоставление или объединение потоков.
Вот некоторые из основных методов Stream и их краткое описание:
- listen() — этот метод используется для обработки событий, передаваемых потоком. Он принимает функцию обратного вызова в качестве аргумента, которая будет вызываться для каждого события, передаваемого потоком.
- where() — этот метод возвращает новый поток, содержащий только события, удовлетворяющие заданному условию.
- map() — этот метод возвращает новый поток, который применяет заданную функцию к каждому событию, генерируемому исходным потоком, и выдает результат этой функции.
- expand() — этот метод возвращает новый поток, который применяет заданную функцию к каждому событию, генерируемому исходным потоком, и выдает результат этой функции в виде потока событий.
- skip() — этот метод возвращает новый поток, который пропускает заданное количество событий, отправленных исходным потоком, перед отправкой оставшихся событий.
- take() — этот метод возвращает новый поток, который выдает только заданное количество событий, отправленных исходным потоком, а затем завершается.
- distinct() — этот метод возвращает новый поток, который генерирует только отдельные события, генерируемые исходным потоком.
- timeout() — этот метод возвращает новый поток, который выдает ошибку, если исходный поток не выдает никаких событий в течение заданного срока.
- firstWhere() — этот метод возвращает объект Future, который завершается первым событием, отправленным потоком, удовлетворяющим заданному условию.
- reduce() — этот метод возвращает объект Future, который завершается результатом применения данной функции ко всем событиям, последовательно передаваемым потоком.
Еще немного про разницу между Stream и StreamController
Подводя итог, можно сказать, что Stream — это способ представления последовательности событий, в то время как контроллер потока — это инструмент для управления этой последовательностью событий.
Класс Stream предоставляет методы для прослушивания событий в потоке, сопоставления и преобразования событий, а также обработки ошибок. Класс StreamController предоставляет методы для добавления данных в поток, а также для прослушивания входящих событий.
Как создать поток в Dart
Чтобы создать поток в Dart, нам нужно использовать комбинацию двух классов: StreamController и Stream. Класс StreamController отвечает за управление потоком, в то время как класс Stream представляет фактический поток данных.
Вот пример того, как создать поток в Dart:
// Создаем stream controller
final StreamController<int> controller = StreamController<int>();
// Создаем stream
final Stream<int> stream = controller.stream;
В этом примере мы создаем новый объект StreamController, который будет обрабатывать целые числа. Затем мы создаем новый объект Stream, вызывая метод получения потока для объекта StreamController.
Как добавить данные в поток в Dart
После того, как мы создали поток, мы можем добавить в него данные, используя метод add в объекте StreamController. Вот пример того, как добавить данные в поток в Dart:
// Добавляем какие-то данные в поток.
controller.add(1);
controller.add(2);
controller.add(3);
В этом примере мы добавляем целые числа 1, 2 и 3 в поток, используя метод add в объекте StreamController.
Как прослушать поток в Dart
Чтобы прослушать поток в Dart, мы можем использовать метод listen для объекта Stream. Метод listen принимает функцию обратного вызова, которая будет вызываться каждый раз, когда в потоке будут доступны новые данные.
Вот пример того, как прослушивать поток в Dart:
// Слушаем поток
stream.listen((int data) {
print('Data received: $data');
});
В этом примере мы используем метод listen для объекта Stream, чтобы зарегистрировать функцию обратного вызова, которая будет вызываться каждый раз, когда в потоке будут доступны новые данные. Функция обратного вызова принимает целочисленный аргумент, представляющий данные, которые были добавлены в поток.
Как закрыть поток в Dart
Когда мы закончим работу с потоком, мы должны закрыть его, чтобы освободить все ресурсы, которые он использует. Мы можем закрыть поток в Dart, вызвав метод close для объекта StreamController.
Вот пример того, как закрыть поток в Dart:
// Закрываем поток
controller.close();
В этом примере мы вызываем метод close для объекта StreamController, чтобы закрыть поток.
Пример использования потока в Dart
Давайте посмотрим на пример того, как использовать потоки в Dart.
import 'dart:async';
void main() {
// Создаем новый StreamController.
final controller = StreamController();
// Добавляем некоторые данные в поток.
controller.add(1);
controller.add(2);
controller.add(3);
// Прослушиваем данные из потока.
final subscription = controller.stream.listen((data) {
print('Received: $data');
});
// Ожидаем две секунды.
Future.delayed(Duration(seconds: 2), () {
// Добавляем еще немного данных в поток.
controller.add(4);
controller.add(5);
controller.add(6);
// Закрываем поток.
controller.close();
});
}
Программа создает StreamController, добавляет некоторые данные в поток с помощью метода add, прослушивает данные из потока с помощью метода listen (класса Stream) и закрывает поток через несколько секунд с помощью метода close. Когда данные добавляются в поток, они выводятся на консоль с помощью функции обратного вызова, предоставляемой методу listen.
Мы также используем Future.delayed, который позволяет подождать несколько секунд, прежде чем добавлять дополнительные данные в поток и закрывать его с помощью метода close.
Когда мы запустим эту программу, мы увидим следующий вывод:
Received: 1
Received: 2
Received: 3
Received: 4
Received: 5
Received: 6
Этот вывод показывает, что мы успешно добавили данные в поток, прослушали эти данные и обработали их по мере поступления.
Широковещательные потоки в Dart
В Dart существует два типа потоков: с одной подпиской и широковещательный поток. Поток с одной подпиской позволяет прослушивать поток только одному слушателю, в то время как широковещательный поток позволяет нескольким слушателям прослушивать поток одновременно.
Широковещательные потоки полезны в сценариях, когда нескольким слушателям необходимо получать одни и те же данные. Например, в приложении для чата нескольким пользователям может потребоваться получить одно и то же сообщение одновременно. В этом случае широковещательный поток может быть использован для отправки сообщения всем пользователям, прослушивающим поток.
Создание широковещательного потока в Dark аналогично созданию потока с одной подпиской. Первым шагом является создание контроллера потока с флагом broadcast, установленным в true. Это создает контроллер потока, который можно использовать для управления широковещательным потоком.
Вот пример создания широковещательного потока в Dart:
import 'dart:async';
void main() {
// Создаем широковещательный StreamController
StreamController<int> controller = StreamController<int>.broadcast(sync: <span class="hljs-literal">true</span>);
// Создаем поток
Stream<int> stream = controller.stream;
// Прослушиваем поток
stream.listen((data) => print('Listener 1: $data'));
stream.listen((data) => print('Listener 2: $data'));
// Добавляем некоторые данные в поток
controller.add(1);
controller.add(2);
controller.add(3);
// Закрываем поток
controller.close();
}
В результате получаем:
Listener 1: 1
Listener 2: 1
Listener 1: 2
Listener 2: 2
Listener 1: 3
Listener 2: 3
В этом примере создается контроллер потока с флагом broadcast, установленным в значение true. Это создает контроллер потока, который можно использовать для управления широковещательным потоком. Затем два слушателя добавляются в поток с помощью метода listen. Когда данные будут добавлены в поток с помощью метода add, оба слушателя получат данные.
Заключение
Потоки и контроллеры потоков — это мощные API в Dart, которые позволяют разработчикам создавать реактивные приложения, способные реагировать на изменения данных в режиме реального времени. Понимая эти API, вы можете написать более чистый и эффективный код, который может обрабатывать большие объемы данных, не блокируя основной поток. Мы надеемся, что эта статья помогла вам лучше понять эти концепции и показала, как эффективно использовать их в ваших программах Dart.