Flutter 中的异步事件处理之广播流(五)

广播流

前面章节, 我们讲的是非广播流的异步、同步处理。接下来, 我们一起探讨广播流的原理和使用。

csharp 复制代码
/// A controller where [stream] can be listened to more than once.
///
/// The [Stream] returned by [stream] is a broadcast stream.
/// It can be listened to more than once.
///
/// A Stream should be inert until a subscriber starts listening on it (using
/// the [onListen] callback to start producing events). Streams should not
/// leak resources (like websockets) when no user ever listens on the stream.
///
/// Broadcast streams do not buffer events when there is no listener.
///
/// The controller distributes any events to all currently subscribed
/// listeners at the time when [add], [addError] or [close] is called.
/// It is not allowed to call `add`, `addError`, or `close` before a previous
/// call has returned. The controller does not have any internal queue of
/// events, and if there are no listeners at the time the event is added,
/// it will just be dropped, or, if it is an error, be reported as uncaught.
///
/// Each listener subscription is handled independently,
/// and if one pauses, only the pausing listener is affected.
/// A paused listener will buffer events internally until unpaused or canceled.
///
/// If [sync] is true, events may be fired directly by the stream's
/// subscriptions during an [add], [addError] or [close] call.
/// The returned stream controller is a [SynchronousStreamController],
/// and must be used with the care and attention necessary to not break
/// the [Stream] contract.
/// See [Completer.sync] for some explanations on when a synchronous
/// dispatching can be used.
/// If in doubt, keep the controller non-sync.
///
/// If [sync] is false, the event will always be fired at a later time,
/// after the code adding the event has completed.
/// In that case, no guarantees are given with regard to when
/// multiple listeners get the events, except that each listener will get
/// all events in the correct order. Each subscription handles the events
/// individually.
/// If two events are sent on an async controller with two listeners,
/// one of the listeners may get both events
/// before the other listener gets any.
/// A listener must be subscribed both when the event is initiated
/// (that is, when [add] is called)
/// and when the event is later delivered,
/// in order to receive the event.
///
/// The [onListen] callback is called when the first listener is subscribed,
/// and the [onCancel] is called when there are no longer any active listeners.
/// If a listener is added again later, after the [onCancel] was called,
/// the [onListen] will be called again.
factory StreamController.broadcast(
    {void onListen()?, void onCancel()?, bool sync = false}) {
  return sync
      ? _SyncBroadcastStreamController<T>(onListen, onCancel)
      : _AsyncBroadcastStreamController<T>(onListen, onCancel);
}

官方文章中, Dart中的StreamController,它用于创建一个可以多次订阅的广播(broadcast)流。以下是文本中的一些重要概念的解释:

  • StreamController是一个控制器,用于创建广播流,广播流可以被多次订阅。
  • 广播流在没有订阅者监听时应该是不活跃的(inert),这意味着它不会在没有用户监听流时泄漏资源,例如Websockets。
  • 广播流不会在没有监听者时缓存事件。
  • 控制器通过在调用addaddErrorclose时,将事件分发给所有当前订阅的监听者。
  • 在前一个addaddErrorclose调用返回之前,不允许调用这些方法,因此不会有内部事件队列。
  • 每个监听者订阅都是独立处理的,如果一个监听者暂停,只有暂停的监听者会受到影响,暂停的监听者会在暂停期间在内部缓存事件,直到恢复或取消。
  • 如果sync参数设置为true,则事件可能会在addaddErrorclose调用期间由流的订阅者直接触发。在这种情况下,返回的流控制器是SynchronousStreamController,必须小心使用以遵守Stream合同。关于何时可以使用同步分派的一些解释可以参考Completer.sync
  • 如果sync参数设置为false,事件将始终在稍后的时间触发,即在添加事件的代码完成后。在这种情况下,不保证多个监听者何时接收事件,但保证每个监听者都会按照正确的顺序接收所有事件。每个订阅都独立处理事件。
  • onListen回调在第一个监听者订阅时调用,而onCancel在没有活动监听者时调用。如果稍后再次添加监听者,onListen将再次被调用。

最后,StreamController.broadcast是用于创建广播流控制器的工厂构造函数,它可以根据需要创建同步或异步广播流控制器。同步广播流控制器在事件添加期间可以立即触发事件,而异步广播流控制器会稍后触发事件。

_StreamControllerLifecycle

typescript 复制代码
abstract class _StreamControllerLifecycle<T> {
  StreamSubscription<T> _subscribe(void onData(T data)?, Function? onError,
      void onDone()?, bool cancelOnError);
  void _recordPause(StreamSubscription<T> subscription) {}
  void _recordResume(StreamSubscription<T> subscription) {}
  Future<void>? _recordCancel(StreamSubscription<T> subscription) => null;
}

这段代码定义了一个名为 _StreamControllerLifecycle<T> 的抽象类,它包含了与流控制器的生命周期相关的方法和属性。

  • _subscribe 方法:这是一个抽象方法,用于订阅流。它接受四个参数,分别是数据到达时的回调函数 onData、错误发生时的回调函数 onError、流结束时的回调函数 onDone,以及一个布尔值 cancelOnError,用于确定是否在发生错误时自动取消订阅。具体实现由子类提供。
  • _recordPause 方法:这是一个空实现的方法,用于记录流的暂停操作。具体实现由子类提供。
  • _recordResume 方法:这是一个空实现的方法,用于记录流的恢复操作。具体实现由子类提供。
  • _recordCancel 方法:这是一个返回Future的方法,用于记录流的取消操作。具体实现由子类提供。如果子类不需要执行异步操作来取消流的订阅,可以返回null

这个抽象类提供了流控制器的生命周期管理方法的接口,具体的实现由子类来完成。这样的设计允许不同类型的流控制器在底层实现上有所不同,但仍然遵循相同的生命周期模式。

_ControllerStream

dart 复制代码
class _ControllerStream<T> extends _StreamImpl<T> {
  _StreamControllerLifecycle<T> _controller;

  _ControllerStream(this._controller);

  StreamSubscription<T> _createSubscription(void onData(T data)?,
          Function? onError, void onDone()?, bool cancelOnError) =>
      _controller._subscribe(onData, onError, onDone, cancelOnError);

  // Override == and hashCode so that new streams returned by the same
  // controller are considered equal. The controller returns a new stream
  // each time it's queried, but doesn't have to cache the result.

  int get hashCode => _controller.hashCode ^ 0x35323532;

  bool operator ==(Object other) {
    if (identical(this, other)) return true;
    return other is _ControllerStream &&
        identical(other._controller, this._controller);
  }
}

这段代码定义了一个名为 _ControllerStream<T> 的类,它是流的实现类,用于将流和流控制器 _StreamControllerLifecycle<T> 关联起来。

  • _controller 属性:这个属性持有一个流控制器对象,用于管理流的生命周期。流的各种操作,如订阅、暂停、恢复等,都通过该控制器来执行。
  • _createSubscription 方法:这个方法用于创建一个流的订阅(StreamSubscription)。它接受四个参数,分别是数据到达时的回调函数 onData、错误发生时的回调函数 onError、流结束时的回调函数 onDone,以及一个布尔值 cancelOnError,用于确定是否在发生错误时自动取消订阅。该方法将这些参数传递给关联的流控制器 _controller_subscribe 方法来创建订阅。
  • hashCode== 方法:这两个方法是为了确保通过相同的流控制器创建的流对象被认为是相等的。这是因为同一个控制器可以创建多个流,它们在功能上是一样的,但是对象不同。为了确保这些流在比较时被视为相等,重写了 hashCode== 方法。hashCode 方法对控制器的哈希码进行了异或操作,以生成流的哈希码。== 方法则比较两个流是否具有相同的控制器。

总之,这个类充当了流与流控制器之间的桥梁,通过将流控制器传递给流的构造函数,可以实现对流的各种操作。同时,通过重写 hashCode== 方法,确保通过同一个控制器创建的流在比较时被认为是相等的。这对于管理多个订阅同一个流的情况非常有用。

_BroadcastStream

scala 复制代码
class _BroadcastStream<T> extends _ControllerStream<T> {
  _BroadcastStream(_StreamControllerLifecycle<T> controller)
      : super(controller);

  bool get isBroadcast => true;
}

上面的代码定义了一个名为_BroadcastStream的类,它是_ControllerStream的子类。这个类用于表示广播(broadcast)流,它通过_StreamControllerLifecycle的实例来初始化。

  • _BroadcastStream 是一个 Dart 类,用于表示广播流。
  • super(controller) 调用了父类 _ControllerStream 的构造函数,并传递了一个 _StreamControllerLifecycle 的实例作为参数,以初始化这个广播流。
  • bool get isBroadcast => true; 定义了一个 getter 方法,用于检查这个流是否是广播流。在这里,它返回true,表示这个流是广播流。

这段代码的主要作用是定义了一个表示广播流的类,该类在初始化时需要一个 _StreamControllerLifecycle 实例,并且可以通过 isBroadcast 属性来检查是否是广播流。

_BroadcastSubscription

dart 复制代码
class _BroadcastSubscription<T> extends _ControllerSubscription<T> {
  static const int _STATE_EVENT_ID = 1;
  static const int _STATE_FIRING = 2;
  static const int _STATE_REMOVE_AFTER_FIRING = 4;
  // TODO(lrn): Use the _state field on _ControllerSubscription to
  // also store this state. Requires that the subscription implementation
  // does not assume that it's use of the state integer is the only use.
  int _eventState = 0; // Initialized to help dart2js type inference.

  _BroadcastSubscription<T>? _next;
  _BroadcastSubscription<T>? _previous;

  _BroadcastSubscription(
      _StreamControllerLifecycle<T> controller,
      void onData(T data)?,
      Function? onError,
      void onDone()?,
      bool cancelOnError)
      : super(controller, onData, onError, onDone, cancelOnError) {
    _next = _previous = this;
  }

  bool _expectsEvent(int eventId) => (_eventState & _STATE_EVENT_ID) == eventId;

  void _toggleEventId() {
    _eventState ^= _STATE_EVENT_ID;
  }

  bool get _isFiring => (_eventState & _STATE_FIRING) != 0;

  void _setRemoveAfterFiring() {
    assert(_isFiring);
    _eventState |= _STATE_REMOVE_AFTER_FIRING;
  }

  bool get _removeAfterFiring =>
      (_eventState & _STATE_REMOVE_AFTER_FIRING) != 0;

  // The controller._recordPause doesn't do anything for a broadcast controller,
  // so we don't bother calling it.
  void _onPause() {}

  // The controller._recordResume doesn't do anything for a broadcast
  // controller, so we don't bother calling it.
  void _onResume() {}

  // _onCancel is inherited.
}

代码定义了一个名为 _BroadcastSubscription<T> 的类,它是广播流的订阅(StreamSubscription)的实现。

  • _STATE_EVENT_ID_STATE_FIRING_STATE_REMOVE_AFTER_FIRING:这些是类的常量字段,用于表示订阅的不同状态。例如,_STATE_EVENT_ID 表示事件的标识符,_STATE_FIRING 表示订阅正在处理事件,而 _STATE_REMOVE_AFTER_FIRING 表示在事件处理后需要将订阅移除。
  • _eventState:这是一个整数字段,用于存储订阅的状态信息。它的值通过与上述常量进行位运算来表示不同的状态。
  • _next_previous:这两个字段用于维护广播订阅之间的链表关系,以便在事件分发时能够正确处理多个订阅。
  • _expectsEvent 方法:这个方法用于检查订阅是否期望接收特定事件。它通过检查 _eventState 中的 _STATE_EVENT_ID 来确定。
  • _toggleEventId 方法:这个方法用于切换 _eventState 中的事件标识符。它在处理多个事件时用于标识不同的事件。
  • _isFiring_setRemoveAfterFiring 方法:_isFiring 方法用于检查订阅是否正在处理事件,而 _setRemoveAfterFiring 方法用于设置在事件处理后需要将订阅移除的标志。
  • _removeAfterFiring 属性:这个属性用于检查订阅是否需要在事件处理后被移除。
  • _onPause_onResume 方法:这两个方法分别用于处理订阅的暂停和恢复操作。对于广播流控制器,它们不执行任何操作。

总之,_BroadcastSubscription<T> 类是广播流的订阅的实现,它包含了管理订阅状态的字段和方法,以及用于处理事件分发和订阅操作的逻辑。这些方法和状态字段用于确保多个订阅可以正确地处理广播事件。

_BroadcastStreamController(广播流控制器抽象类)

csharp 复制代码
static const int _STATE_INITIAL = 0;
static const int _STATE_EVENT_ID = 1;
static const int _STATE_FIRING = 2;
static const int _STATE_CLOSED = 4;
static const int _STATE_ADDSTREAM = 8;

void Function()? onListen;
FutureOr<void> Function()? onCancel;

// State of the controller.
int _state;

这段代码定义了一些常量和字段,用于表示控制器(StreamController)的不同状态和事件回调:

  • _STATE_INITIAL_STATE_EVENT_ID_STATE_FIRING_STATE_CLOSED_STATE_ADDSTREAM:这些是控制器的不同状态的常量,用于表示控制器的状态。例如,_STATE_INITIAL 表示初始状态,_STATE_EVENT_ID 表示事件标识符状态,_STATE_FIRING 表示事件处理中的状态,_STATE_CLOSED 表示控制器已关闭的状态,_STATE_ADDSTREAM 表示正在添加流的状态。
  • onListen:这是一个可选的回调函数,它在订阅者开始监听流时被调用。您可以在这里执行一些初始化操作或处理订阅事件。
  • onCancel:这是一个可选的回调函数,它在所有订阅者都取消订阅流时被调用。通常在这里执行一些资源清理操作。
  • _state:这是一个整数字段,用于表示控制器的当前状态。它的值会根据不同的操作和事件而改变,以反映控制器的状态。

这些常量和字段用于管理 StreamController 的状态和事件回调。根据不同的状态和事件,控制器可以执行不同的操作以确保正确的流控制和事件分发。

_firstSubscription、_lastSubscription

这段代码定义了一个双向链表,用于管理活动的监听器(_BroadcastSubscription)。

  • _firstSubscription:这是链表中的第一个监听器,即链表的头部。它是一个 _BroadcastSubscription 对象的引用。
  • _lastSubscription:这是链表中的最后一个监听器,即链表的尾部。它也是一个 _BroadcastSubscription 对象的引用。

这个双向链表用于跟踪当前活动的监听器。当新的监听器订阅广播流时,它将被添加到这个链表的末尾。当监听器取消订阅流时,它将从链表中移除。这样,控制器可以有效地管理多个监听器,确保事件被分发给所有活动的监听器。

stream、sink

dart 复制代码
Stream<T> get stream => new _BroadcastStream<T>(this);

StreamSink<T> get sink => new _StreamSinkWrapper<T>(this);

这段代码定义了两个 getter 方法:streamsink,它们用于获取与 _BroadcastStreamController 关联的广播流(Stream<T>)和流水槽(StreamSink<T>)。

  • stream 方法返回一个新的 _BroadcastStream 对象,该对象连接到当前的 _BroadcastStreamController 实例。这个广播流可以被多个订阅者监听,因此它是一个广播流。
  • sink 方法返回一个新的 _StreamSinkWrapper 对象,该对象连接到当前的 _BroadcastStreamController 实例。这个流水槽可用于向广播流添加事件或错误,从而控制流的数据。

这两个方法提供了访问与控制器关联的广播流和流水槽的方式,允许您在控制器上执行添加事件、关闭流等操作。

isClosed、isPaused、hasListener、_hasOneListener、_isFiring、_isEmpty

arduino 复制代码
Stream<T> get stream => new _BroadcastStream<T>(this);

StreamSink<T> get sink => new _StreamSinkWrapper<T>(this);

bool get isClosed => (_state & _STATE_CLOSED) != 0;

/// A broadcast controller is never paused.
///
/// Each receiving stream may be paused individually, and they handle their
/// own buffering.
bool get isPaused => false;

/// Whether there are currently one or more subscribers.
bool get hasListener => !_isEmpty;

/// Test whether the stream has exactly one listener.
///
/// Assumes that the stream has a listener (not [_isEmpty]).
bool get _hasOneListener {
  assert(!_isEmpty);
  return identical(_firstSubscription, _lastSubscription);
}

bool get _isEmpty => _firstSubscription == null;
  • isClosed: 这个属性检查 _state 变量,如果流控制器已关闭,则返回 true,否则返回 false
  • isPaused: 对于广播流控制器,它总是返回 false因为广播流不会被暂停。此属性用于查询流是否处于暂停状态。
  • hasListener: 这个属性检查是否当前有一个或多个订阅者。如果至少有一个订阅者,它返回 true,否则返回 false
  • _hasOneListener: 这个属性检查是否流控制器只有一个订阅者。它假定流至少有一个订阅者,如果流只有一个订阅者,它返回 true
  • _isFiring: 这个属性检查是否正在触发事件,即事件正在发送给一些但不是所有订阅者。如果是这种情况,它返回 true,否则返回 false

_addListener(添加订阅者,双向链表,核心)

ini 复制代码
/// Adds subscription to linked list of active listeners.
void _addListener(_BroadcastSubscription<T> subscription) {
  assert(identical(subscription._next, subscription));
  subscription._eventState = (_state & _STATE_EVENT_ID);
  // Insert in linked list as last subscription.
  _BroadcastSubscription<T>? oldLast = _lastSubscription;
  _lastSubscription = subscription;
  subscription._next = null;
  subscription._previous = oldLast;
  if (oldLast == null) {
    _firstSubscription = subscription;
  } else {
    oldLast._next = subscription;
  }
}

这段代码用于将一个订阅者(_BroadcastSubscription)添加到活动监听器的双向链表中。具体的步骤如下:

  • 首先,它确保要添加的订阅者的 _next 属性等于自身,这是为了确保订阅者尚未添加到链表中。
  • 然后,它获取流控制器的当前状态,并将状态保存到订阅者的 _eventState 属性中。这是为了记录事件的状态。
  • 接下来,它将订阅者插入到链表中,作为最后一个订阅者。首先,它将旧的最后一个订阅者保存在 oldLast 变量中。
  • 然后,它将流控制器的 _lastSubscription 属性更新为新的订阅者,表示新的最后一个订阅者。
  • 接着,它将新的订阅者的 _next 设置为 null,表示它是链表中的最后一个元素。
  • 如果旧的最后一个订阅者不存在(即这是第一个订阅者),则将流控制器的 _firstSubscription 属性设置为新的订阅者,表示链表的第一个元素。
  • 如果旧的最后一个订阅者存在(即不是第一个订阅者),则将旧的最后一个订阅者的 _next 设置为新的订阅者,以建立链表中的链接。

通过执行这些步骤,订阅者被添加到流控制器的链表中, 双向链表就连接起来了。这允许流控制器将事件广播给所有订阅者。

_removeListener(移除订阅者,双向链表,核心)

ini 复制代码
void _removeListener(_BroadcastSubscription<T> subscription) {
  assert(identical(subscription._controller, this));
  assert(!identical(subscription._next, subscription));
  _BroadcastSubscription<T>? previous = subscription._previous;
  _BroadcastSubscription<T>? next = subscription._next;
  if (previous == null) {
    // This was the first subscription.
    _firstSubscription = next;
  } else {
    previous._next = next;
  }
  if (next == null) {
    // This was the last subscription.
    _lastSubscription = previous;
  } else {
    next._previous = previous;
  }

  subscription._next = subscription._previous = subscription;
}

这段代码用于从活动监听器的链表中移除一个订阅者(_BroadcastSubscription)。具体的步骤如下:

  • 首先,它确保要移除的订阅者的 _controller 属性与当前的流控制器(this)相同,以确保不会意外地从其他流控制器中移除订阅者。
  • 接下来,它确保订阅者的 _next 属性不等于自身,以确保该订阅者实际上已经添加到链表中。
  • 然后,它获取订阅者的前一个订阅者和后一个订阅者,分别保存在 previousnext 变量中。
  • 如果 previousnull,说明要移除的订阅者是链表中的第一个订阅者,因此将流控制器的 _firstSubscription 属性更新为 next,从而将链表的第一个元素设为 next
  • 如果 previous 不为 null,则将 previous_next 属性设置为 next,从而将链表中的前一个订阅者与后一个订阅者连接起来,跳过要移除的订阅者。
  • 如果 nextnull,说明要移除的订阅者是链表中的最后一个订阅者,因此将流控制器的 _lastSubscription 属性更新为 previous,从而将链表的最后一个元素设为 previous
  • 如果 next 不为 null,则将 next_previous 属性设置为 previous,从而将链表中的后一个订阅者与前一个订阅者连接起来,跳过要移除的订阅者。
  • 最后,它将要移除的订阅者的 _next_previous 属性都设置为自身,以确保该订阅者不再与链表中的其他订阅者连接。

通过执行这些步骤,订阅者被从流控制器的链表中成功移除,不再接收事件。

_subscribe(新建订阅者)

javascript 复制代码
StreamSubscription<T> _subscribe(void onData(T data)?, Function? onError,
    void onDone()?, bool cancelOnError) {
  if (isClosed) {
    return new _DoneStreamSubscription<T>(onDone);
  }
  var subscription = new _BroadcastSubscription<T>(
      this, onData, onError, onDone, cancelOnError);
  _addListener(subscription);
  if (identical(_firstSubscription, _lastSubscription)) {
    // Only one listener, so it must be the first listener.
    _runGuarded(onListen);
  }
  return subscription;
}

这段代码是流控制器 _BroadcastStreamController 类的 _subscribe 方法。该方法用于订阅流(Stream),并返回一个 StreamSubscription 对象,该对象用于管理事件监听和取消订阅。

具体步骤如下:

  • 首先,它检查流控制器是否已关闭(isClosed)。如果流已关闭,它将返回一个 _DoneStreamSubscription,这是一个特殊的 StreamSubscription,用于表示流已经完成,不会再产生事件。
  • 然后,它创建一个新的 _BroadcastSubscription 对象,这是 _ControllerSubscription 的子类,用于管理订阅的具体事件处理逻辑。将订阅者的 onDataonErroronDonecancelOnError 参数传递给 _BroadcastSubscription 的构造函数。
  • 接下来,它调用 _addListener 方法将新创建的订阅者添加到活动监听器链表中。
  • 如果当前只有一个监听器(identical(_firstSubscription, _lastSubscription) 返回 true),则说明这是第一个监听器,因此它会调用 _runGuarded(onListen) 来触发 onListen 回调。_runGuarded 方法用于执行回调函数并捕获任何可能抛出的异常。
  • 最后,它返回新创建的订阅者对象,使调用者可以使用该对象来管理事件的监听和取消订阅。

这个方法的主要作用是创建并添加订阅者,并在有必要时触发 onListen 回调。这样,订阅者就可以开始接收流中的事件了。

_recordCancel

scss 复制代码
Future<void>? _recordCancel(StreamSubscription<T> sub) {
  _BroadcastSubscription<T> subscription = sub as _BroadcastSubscription<T>;
  // If already removed by the stream, don't remove it again.
  if (identical(subscription._next, subscription)) return null;
  if (subscription._isFiring) {
    subscription._setRemoveAfterFiring();
  } else {
    _removeListener(subscription);
    // If we are currently firing an event, the empty-check is performed at
    // the end of the listener loop instead of here.
    if (!_isFiring && _isEmpty) {
      _callOnCancel();
    }
  }
  return null;
}

这段代码是流控制器 _StreamController 类的 _recordCancel 方法。该方法用于处理取消订阅操作,即当订阅者主动取消订阅时。

具体步骤如下:

  • 首先,它将传入的 StreamSubscription 对象 sub 强制类型转换为 _BroadcastSubscription<T> 类型的 subscription。这是因为 _BroadcastSubscription 包含了更多有关订阅的信息。
  • 接下来,它检查 subscription 是否已经被从监听链表中移除,通过检查 subscription._next 是否等于 subscription 来判断。如果已经移除,则直接返回 null,不执行任何操作,因为不需要再次移除。
  • 如果 subscription 正在触发事件(subscription._isFiring 返回 true),则设置 subscription 的状态为 _STATE_REMOVE_AFTER_FIRING,表示在事件触发完成后再将其移除。
  • 如果 subscription 不在触发事件,它调用 _removeListener 方法将订阅者从监听链表中移除。然后,它检查是否需要触发 onCancel 回调,这通常在没有任何监听器时才会触发。
  • 最后,它返回 null,表示取消订阅操作已完成。

这个方法主要用于处理取消订阅的逻辑,确保订阅者在取消订阅时被正确地从监听链表中移除,并在必要时触发 onCancel 回调。

add

scss 复制代码
void add(T data) {
  if (!_mayAddEvent) throw _addEventError();
  _sendData(data);
}

这段代码是流控制器 _StreamController 类的 add 方法。该方法用于向流中添加数据事件。

具体步骤如下:

  • 首先,它检查 _mayAddEvent 是否为 true,如果不是,就抛出一个异常 _addEventError()。这个检查通常用于确保在某些情况下不会向流中添加事件,例如在流已关闭时。
  • 如果 _mayAddEventtrue,则调用 _sendData(data) 方法来将数据事件发送到流中。这个方法负责将事件分发给所有的订阅者。

总之,add 方法用于添加数据事件到流中,但在添加之前会进行一些条件检查,以确保添加事件的操作是有效的。如果条件不符合,就会抛出异常。

addError

ini 复制代码
void addError(Object error, [StackTrace? stackTrace]) {
  checkNotNullable(error, "error");
  if (!_mayAddEvent) throw _addEventError();
  AsyncError? replacement = Zone.current.errorCallback(error, stackTrace);
  if (replacement != null) {
    error = replacement.error;
    stackTrace = replacement.stackTrace;
  } else {
    stackTrace ??= AsyncError.defaultStackTrace(error);
  }
  _sendError(error, stackTrace);
}

这段代码是流控制器 _StreamController 类的 addError 方法。该方法用于向流中添加错误事件。

具体步骤如下:

  • 首先,它调用 checkNotNullable 函数来检查 error 参数是否为非空(不为 null),如果 errornull,则会抛出一个异常。
  • 接下来,它检查 _mayAddEvent 是否为 true,如果不是,就抛出一个异常 _addEventError()。这个检查通常用于确保在某些情况下不会向流中添加事件,例如在流已关闭时。
  • 然后,它调用 Zone.current.errorCallback 方法,该方法用于在 Dart 中处理错误和异常。它传递 errorstackTrace 参数,以获取可能的替代错误和堆栈跟踪。
  • 如果 Zone.current.errorCallback 返回了一个非空的 replacement,则将 errorstackTrace 更新为替代值。否则,将 stackTrace 设置为默认的堆栈跟踪。
  • 最后,它调用 _sendError(error, stackTrace) 方法来将错误事件发送到流中。这个方法负责将事件分发给所有的订阅者。

addError 方法用于向流中添加错误事件,但在添加之前会进行一些条件检查,以确保添加事件的操作是有效的。如果条件不符合,就会抛出异常。此外,它还支持通过 Zone 进行错误处理,并可以处理替代错误和堆栈跟踪。

_forEachListener(同步流用到)

scss 复制代码
// Event handling.
void _forEachListener(
    void action(_BufferingStreamSubscription<T> subscription)) {
  if (_isFiring) {
    throw new StateError(
        "Cannot fire new event. Controller is already firing an event");
  }
  if (_isEmpty) return;

  // Get event id of this event.
  int id = (_state & _STATE_EVENT_ID);
  // Start firing (set the _STATE_FIRING bit). We don't do [onCancel]
  // callbacks while firing, and we prevent reentrancy of this function.
  //
  // Set [_state]'s event id to the next event's id.
  // Any listeners added while firing this event will expect the next event,
  // not this one, and won't get notified.
  _state ^= _STATE_EVENT_ID | _STATE_FIRING;
  _BroadcastSubscription<T>? subscription = _firstSubscription;
  while (subscription != null) {
    if (subscription._expectsEvent(id)) {
      subscription._eventState |= _BroadcastSubscription._STATE_FIRING;
      action(subscription);
      subscription._toggleEventId();
      _BroadcastSubscription<T>? next = subscription._next;
      if (subscription._removeAfterFiring) {
        _removeListener(subscription);
      }
      subscription._eventState &= ~_BroadcastSubscription._STATE_FIRING;
      subscription = next;
    } else {
      subscription = subscription._next;
    }
  }
  _state &= ~_STATE_FIRING;

  if (_isEmpty) {
    _callOnCancel();
  }
}

Dart 流控制器中的事件处理逻辑。它用于处理向流中添加事件并通知已订阅该流的监听器。

具体步骤如下:

  1. 首先,代码检查 _isFiring 是否为 true,如果已经在处理事件("firing")过程中,就会抛出一个状态错误,因为不允许在处理事件期间添加新的事件。

  2. 接下来,代码检查 _isEmpty 是否为 true,如果流为空(没有订阅者),则直接返回,因为没有需要通知的监听器。

  3. 获取当前事件的标识符 id,这个标识符用于标记事件,以便监听器知道它们是否应该处理这个事件。

  4. 设置流控制器的状态 _state,将 _STATE_FIRING 标志位设置为 true,表示正在处理事件,并清除当前事件的标识符 _STATE_EVENT_ID

  5. 开始迭代所有的订阅者(监听器)。对于每个订阅者:

    • 检查订阅者是否期望处理当前事件,即它是否等待处理具有与当前事件相同标识符的事件。
    • 如果订阅者期望处理当前事件,将其标记为正在处理 _STATE_FIRING
    • 执行 action(subscription),这个 action 函数是用于处理事件的回调函数,它会通知订阅者有新事件到达。
    • 标记订阅者已经处理完当前事件,并切换到下一个事件的标识符。
    • 如果订阅者在处理事件后需要被移除(_removeAfterFiringtrue),则从监听器列表中移除该订阅者。
    • 继续处理下一个订阅者。
  6. 清除流控制器的 _STATE_FIRING 标志位,表示事件处理完成。

  7. 如果流为空(没有订阅者),则调用 _callOnCancel(),这是流控制器上的回调函数,用于处理取消订阅的操作。

总之,这段代码负责将事件分发给所有已订阅该流的监听器,并确保事件按照正确的顺序传递给监听器。如果在处理事件期间有新事件到达,它会等待当前事件处理完毕后再处理新事件。

异步广播流控制器_AsyncBroadcastStreamController(广播流控制器实现类)

javascript 复制代码
class _AsyncBroadcastStreamController<T> extends _BroadcastStreamController<T> {
  _AsyncBroadcastStreamController(void onListen()?, void onCancel()?)
      : super(onListen, onCancel);

  // EventDispatch interface.

  void _sendData(T data) {
    for (var subscription = _firstSubscription;
        subscription != null;
        subscription = subscription._next) {
      subscription._addPending(new _DelayedData<T>(data));
    }
  }

  void _sendError(Object error, StackTrace stackTrace) {
    for (var subscription = _firstSubscription;
        subscription != null;
        subscription = subscription._next) {
      subscription._addPending(new _DelayedError(error, stackTrace));
    }
  }

  void _sendDone() {
    if (!_isEmpty) {
      for (var subscription = _firstSubscription;
          subscription != null;
          subscription = subscription._next) {
        subscription._addPending(const _DelayedDone());
      }
    } else {
      assert(_doneFuture != null && _doneFuture!._mayComplete);
      _doneFuture!._asyncComplete(null);
    }
  }
}

创建异步广播流(_AsyncBroadcastStreamController)的实现代码。广播流允许多个监听器同时订阅,当有事件产生时,会通知所有订阅者。

下面是这段代码的主要功能:

  1. _AsyncBroadcastStreamController 类继承自 _BroadcastStreamController,表示创建的是异步广播流。
  2. 构造函数接受两个可选的回调函数 onListenonCancel,用于在订阅开始和取消时执行特定操作。
  3. _sendData 方法用于发送数据事件给所有已订阅的监听器。它遍历所有订阅者,并将数据事件包装成 _DelayedData 对象添加到各个监听器的事件队列中。
  4. _sendError 方法用于发送错误事件给所有已订阅的监听器。它遍历所有订阅者,并将错误事件包装成 _DelayedError 对象添加到各个监听器的事件队列中。
  5. _sendDone 方法用于发送流关闭事件。如果流中有订阅者(即不为空),则遍历所有订阅者,并将流关闭事件包装成 _DelayedDone 对象添加到各个监听器的事件队列中。如果流为空,表示没有订阅者,那么它会直接完成流的 _doneFuture(这是一个 Future,用于表示流关闭),通过调用 _doneFuture!._asyncComplete(null) 来完成流。

这段代码实现了异步广播流的核心功能,它可以将事件(数据、错误、关闭)发送给所有已订阅的监听器,实现了多订阅者之间的事件通知。

同步广播流控制器_AsyncBroadcastStreamController(广播流控制器实现类)

scss 复制代码
class _SyncBroadcastStreamController<T> extends _BroadcastStreamController<T>
    implements SynchronousStreamController<T> {
  _SyncBroadcastStreamController(void onListen()?, void onCancel()?)
      : super(onListen, onCancel);

  // EventDispatch interface.

  bool get _mayAddEvent => super._mayAddEvent && !_isFiring;

  _addEventError() {
    if (_isFiring) {
      return new StateError(
          "Cannot fire new event. Controller is already firing an event");
    }
    return super._addEventError();
  }

  void _sendData(T data) {
    if (_isEmpty) return;
    if (_hasOneListener) {
      _state |= _BroadcastStreamController._STATE_FIRING;
      _BroadcastSubscription<T> firstSubscription =
          _firstSubscription as dynamic;
      firstSubscription._add(data);
      _state &= ~_BroadcastStreamController._STATE_FIRING;
      if (_isEmpty) {
        _callOnCancel();
      }
      return;
    }
    _forEachListener((_BufferingStreamSubscription<T> subscription) {
      subscription._add(data);
    });
  }

  void _sendError(Object error, StackTrace stackTrace) {
    if (_isEmpty) return;
    _forEachListener((_BufferingStreamSubscription<T> subscription) {
      subscription._addError(error, stackTrace);
    });
  }

  void _sendDone() {
    if (!_isEmpty) {
      _forEachListener((_BufferingStreamSubscription<T> subscription) {
        subscription._close();
      });
    } else {
      assert(_doneFuture != null && _doneFuture!._mayComplete);
      _doneFuture!._asyncComplete(null);
    }
  }
}

创建同步广播流(_SyncBroadcastStreamController)的实现代码。同步广播流与异步广播流类似,允许多个监听器同时订阅,但与异步流不同的是,同步流在事件分发时会同步执行事件处理函数,而不会使用异步队列。

下面是这段代码的主要功能:

  1. _SyncBroadcastStreamController 类继承自 _BroadcastStreamController 并实现了 SynchronousStreamController<T> 接口,表示创建的是同步广播流。
  2. 构造函数接受两个可选的回调函数 onListenonCancel,用于在订阅开始和取消时执行特定操作。
  3. _mayAddEvent 方法用于确定是否可以添加新的事件。在同步广播流中,只有在没有事件正在处理(!_isFiring)的情况下才能添加新事件。
  4. _addEventError 方法用于生成添加事件时的错误消息。如果已经有事件正在处理,它会抛出一个 StateError,表示无法添加新事件。
  5. _sendData 方法用于发送数据事件给所有已订阅的监听器。如果流为空,不会执行任何操作。如果只有一个监听器,它将在当前线程中直接调用监听器的事件处理函数,并将数据传递给它。如果有多个监听器,它会遍历所有监听器并调用它们的事件处理函数。
  6. _sendError 方法用于发送错误事件给所有已订阅的监听器。如果流为空,不会执行任何操作。它会遍历所有监听器并将错误事件传递给它们。
  7. _sendDone 方法用于发送流关闭事件。如果流不为空,它会遍历所有监听器并通知它们流已关闭。如果流为空,它将直接完成流的 _doneFuture(这是一个 Future,用于表示流关闭),通过调用 _doneFuture!._asyncComplete(null) 来完成流。

这段代码实现了同步广播流的核心功能,它可以将事件(数据、错误、关闭)发送给所有已订阅的监听器,事件处理是同步执行的,不使用异步队列。这对于需要立即处理事件的情况非常有用。

总结

同步广播流(_SyncBroadcastStreamController) 和 异步广播流(_AsyncBroadcastStreamController)的相同点、不同点

同步广播流(_SyncBroadcastStreamController)和异步广播流(_AsyncBroadcastStreamController)是用于创建广播流的两种不同类型的流控制器。它们有一些相同点和不同点,下面是它们的比较:

相同点:

  1. 广播流: 无论是同步广播流还是异步广播流,它们都创建的是广播流,这意味着可以有多个监听器同时订阅这些流。
  2. 监听器管理: 两者都可以管理多个监听器,每个监听器都能独立地接收事件。
  3. 事件分发: 无论是同步广播流还是异步广播流,它们都可以将事件(数据、错误、关闭)发送给所有已订阅的监听器。
  4. 回调支持: 它们都支持在订阅开始和取消时执行特定的回调函数,即 onListenonCancel

不同点:

  1. 同步 vs. 异步: 最大的区别在于事件的分发方式。同步广播流会在事件分发时同步执行事件处理函数,而不会使用异步队列。这意味着在同步广播流中,事件处理函数会立即执行,而在异步广播流中,事件处理函数会在未来的某个时间异步执行。
  2. 性能: 同步广播流在事件分发方面更为高效,因为它避免了异步调度的开销。但是,如果事件处理函数需要执行耗时的操作,同步广播流可能会阻塞整个事件处理过程,而异步广播流能够确保不阻塞事件处理。
  3. 使用场景: 同步广播流适用于需要立即处理事件、事件处理函数执行速度较快且不会阻塞的情况。异步广播流适用于需要处理耗时操作、不希望阻塞事件分发的情况。

总的来说,选择同步广播流还是异步广播流取决于你的具体需求。如果事件处理需要快速执行并且不会导致阻塞,那么同步广播流可能是更好的选择。如果事件处理可能会耗时较长或需要异步执行,那么异步广播流可能更适合。

非广播流、广播流的相同点、不同点

  • 相同点:

    1. 数据流: 广播流和非广播流都表示数据流,可用于传输数据、事件或信息。
    2. 事件处理: 在订阅流时,可以指定处理事件的回调函数,通常包括 onDataonErroronDone 回调。
  • 不同点:

    1. 订阅模式:
    • 非广播流只能被单个监听器(订阅者)监听。一旦有一个监听器订阅了非广播流,其他监听器就无法再订阅相同的流。
    • 广播流可以同时被多个监听器(订阅者)监听,这意味着多个监听器可以独立地接收相同的事件或数据流。

参考链接

Flutter完整开发实战详解(十一、全面深入理解Stream)

相关推荐
开心工作室_kaic15 分钟前
ssm068海鲜自助餐厅系统+vue(论文+源码)_kaic
前端·javascript·vue.js
有梦想的刺儿34 分钟前
webWorker基本用法
前端·javascript·vue.js
cy玩具1 小时前
点击评论详情,跳到评论页面,携带对象参数写法:
前端
qq_390161772 小时前
防抖函数--应用场景及示例
前端·javascript
John.liu_Test2 小时前
js下载excel示例demo
前端·javascript·excel
Yaml42 小时前
智能化健身房管理:Spring Boot与Vue的创新解决方案
前端·spring boot·后端·mysql·vue·健身房管理
PleaSure乐事2 小时前
【React.js】AntDesignPro左侧菜单栏栏目名称不显示的解决方案
前端·javascript·react.js·前端框架·webstorm·antdesignpro
哟哟耶耶2 小时前
js-将JavaScript对象或值转换为JSON字符串 JSON.stringify(this.SelectDataListCourse)
前端·javascript·json
getaxiosluo2 小时前
react jsx基本语法,脚手架,父子传参,refs等详解
前端·vue.js·react.js·前端框架·hook·jsx
理想不理想v2 小时前
vue种ref跟reactive的区别?
前端·javascript·vue.js·webpack·前端框架·node.js·ecmascript