Avatar
1
uzumaki258 Beginner
uzumaki258 Beginner
Handle concurrent event Flutter
Chào mọi người,
Có ai từng làm việc với Bloc library và phải handle concurrent event chưa ạ?
Mỗi event cần xử lý call API, em có dùng async để cancel future call trong trường hợp user bấm tạo event nhiều rồi. Nhưng vẫn gặp phải trường hợp future cancel ko nhanh bằng event mới được add thêm vào => nhiều event 1 lúc handle dẫn đến sai logic.
Vi dụ: user upload ảnh 1 qua API, nhưng user cancel ảnh nhanh và chọn ảnh 2, thì sẽ có trường hợp e ko cancel được event của ảnh 1 dẫn đến sai logic.
Mong mọi người giúp đỡ ạ.
  • Answer
Remain: 5
3 Answers
Avatar
monkey Beginner
monkey Beginner
Em có thể cung cấp source code của em để anh tái hiện lại cho nhanh không em? Sử dụng thẻ pre để paste code em nhé.
  • 0
  • Reply
cảm ơn a đã phản hồi a, E có 1 ví dụ ntn:
import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';

class TestEvent {}

class TestBloc extends Bloc {
  TestBloc() : super(Object()) {
    on(_onTestEvent, transformer: restartable());
  }

  var testCounter = 0;

  Future _onTestEvent(event, emit) async {
    final counter = testCounter++;
    print('Starting test count: $counter, is emitter alive: ${!emit.isDone}');
    await Future.delayed(const Duration(seconds: 5));
    if (emit.isDone) return;
    // We should reach here only for latest event
    print('Finished test count: $counter, is emitter alive: ${!emit.isDone}');
  }
}

void main() {
  final bloc = TestBloc();
  bloc.stream.listen(null);
  bloc.add(TestEvent());
  bloc.add(TestEvent());
  bloc.add(TestEvent());
}

Nhưng khi e chạy thì e thấy nó ko cancel được TestEvent cũ

// Expected to print:

// Starting test count: 0, is emitter alive: true

// Starting test count: 1, is emitter alive: true

// Starting test count: 2, is emitter alive: true

// Finished test count: 2, is emitter alive: true

// Actually prints:

// Starting test count: 0, is emitter alive: true

// Starting test count: 1, is emitter alive: true

// Starting test count: 2, is emitter alive: true

// Finished test count: 0, is emitter alive: false

// Finished test count: 1, is emitter alive: false

// Finished test count: 2, is emitter alive: true

Mong a cho ý kiến ạ

 –  uzumaki258 1666121636000
Không hiểu sao lần nào anh chạy cũng ra:

Starting test count: 0, is emitter alive: true
Starting test count: 1, is emitter alive: true
Starting test count: 2, is emitter alive: true
Finished test count: 2, is emitter alive: true

Có lưu ý gì đặc biệt khi chạy không em?

Anh đang dùng:

environment:
  sdk: '>=2.18.2 <3.0.0'

dev_dependencies:
  lints: ^2.0.0
  test: ^1.16.0
  bloc: ^8.1.0
  bloc_concurrency: ^0.2.0
</pre>
                             – 
                            
                                monkey
                            
                            
                                1666124358000
                            
                            
                        
E vừa viết lại example cho nó khớp với case của e. Bởi vì app thực tế nó nhiều event hơn. Không dùng được await như trên được, ko là nó bị kẹt event handle
import 'package:bloc/bloc.dart';
import 'package:bloc_concurrency/bloc_concurrency.dart';

abstract class BaseEvent {}

class PickImageEvent extends BaseEvent {}

class IdentifyImageEvent extends BaseEvent {}

class ShowResultEvent extends BaseEvent {}

class BaseState {}

class TestBloc extends Bloc {
  TestBloc() : super(BaseState()) {
    on(_onPickImageEvent, transformer: restartable());
    on(_onIdentifyImageEvent, transformer: restartable());
    on(_onShowResultEvent, transformer: restartable());
  }

  var testCounter = 0;

  Future _onPickImageEvent(
    PickImageEvent event,
    Emitter emit,
  ) async {
    final counter = ++testCounter;
    print('Start picking image | Counter $counter');
    await Future.delayed(const Duration(seconds: 5));
    print('Have picked an image | Counter $counter');
    add(IdentifyImageEvent());
  }

  Future _onIdentifyImageEvent(
    IdentifyImageEvent event,
    Emitter emit,
  ) async {
    print('> Start identifying that image | Counter $testCounter');
    await Future.delayed(const Duration(seconds: 3));
    print('> Finished identifying that image | Counter $testCounter');
    add(ShowResultEvent());
  }

  Future _onShowResultEvent(
    ShowResultEvent event,
    Emitter emit,
  ) async {
    print('>> Start showing result | Counter $testCounter');
    await Future.delayed(const Duration(seconds: 2));
    print('>> Stopped showing result | Counter $testCounter');
  }
}

/// Expect result:
/// Start picking image | Counter 1
/// Start picking image | Counter 2
/// Start picking image | Counter 3
///
/// Have picked an image | Counter 3
///
/// > Start identifying that image | Counter 3
/// > Finished identifying that image | Counter 3
/// 
/// >> Start showing result | Counter 3
/// >> Stopped showing result | Counter 3

void main() {
  final bloc = TestBloc();
  bloc.stream.listen(null);
  bloc.add(PickImageEvent());
  bloc.add(PickImageEvent());
  bloc.add(PickImageEvent());
}

A xem giúp e nha, cảm ơn a

 –  uzumaki258 1666125878000
Avatar
monkey Beginner
monkey Beginner
The Best Answer
Thực tế async này nó sẽ sử dụng các luồng ở background để lập lịch, sau đó đến thời điểm nó sẽ đưa các event vào main queue từ đó mà các event này được chạy trên luồng chính. Chính vì điều này mà code của em bị chạy lung tung, vì việc xử lý ở background có thời gian hoàn thành khác nhau, dẫn đến các sự kiện xảy ra trên luồng chính sẽ không theo tuần tự.

Em thử code này xem sao nhé:

<span>import</span> <span>'package</span>:bloc/bloc.dart';
<span>import</span> <span>'package</span>:bloc_concurrency/bloc_concurrency.dart';

<span>abstract</span> <span><span>class</span> <span>BaseEvent</span> </span>{}

<span><span>class</span> <span>PickImageEvent</span> <span>extends</span> <span>BaseEvent</span> </span>{}

<span><span>class</span> <span>IdentifyImageEvent</span> <span>extends</span> <span>BaseEvent</span> </span>{}

<span><span>class</span> <span>ShowResultEvent</span> <span>extends</span> <span>BaseEvent</span> </span>{}

<span><span>class</span> <span>BaseState</span> </span>{
}

<span><span>class</span> <span>BlockProxy</span> <span>extends</span> <span>Bloc<BaseEvent</span>, <span>BaseState></span> </span>{

  <span>var</span> _counter = <span>0</span>;

  <span>BlockProxy</span>(<span>var</span> testCounter) : <span>super</span>(<span>BaseState</span>()) {
    _counter = testCounter;
    on<<span>PickImageEvent</span>>(_onPickImageEvent, transformer: restartable());
    on<<span>IdentifyImageEvent</span>>(_onIdentifyImageEvent, transformer: restartable());
    on<<span>ShowResultEvent</span>>(_onShowResultEvent, transformer: restartable());
  }

  <span>Future</span> _onPickImageEvent(event, emit) async {
    print(<span>'Start</span> picking image | <span>Counter</span> $_counter');
    await <span>Future</span>.delayed(const <span>Duration</span>(seconds: <span>5</span>));
    <span>if</span> (!isClosed) {
      print(<span>'Have</span> picked an image | <span>Counter</span> $_counter');
      add(<span>IdentifyImageEvent</span>());
    }
  }

  <span>Future</span> _onIdentifyImageEvent(event, emit) async {
    print('> <span>Start</span> identifying that image | <span>Counter</span> $_counter');
    await <span>Future</span>.delayed(const <span>Duration</span>(seconds: <span>3</span>));
    <span>if</span> (!isClosed) {
      print('> <span>Finished</span> identifying that image | <span>Counter</span> $_counter');
      add(<span>ShowResultEvent</span>());
    }
  }

  <span>Future</span> _onShowResultEvent(event, emit) async {
    print('>> <span>Start</span> showing result | <span>Counter</span> $_counter');
    await <span>Future</span>.delayed(const <span>Duration</span>(seconds: <span>2</span>));
    <span>if</span> (!isClosed) {
      print('>> <span>Stopped</span> showing result | <span>Counter</span> $_counter');
    }
  }
}

<span><span>class</span> <span>TestBloc</span> </span>{

  <span>List</span><<span>BlockProxy</span>> blocList = [];

  <span>var</span> testCounter = <span>0</span>;

  void add(<span>PickImageEvent</span> event) async {
    <span>for</span> (<span>BlockProxy</span> element in blocList) {
      <span>if</span> (!element.isClosed) {
        element.close();
      }
    }
    <span>var</span> bloc = <span>BlockProxy</span>(++testCounter);
    blocList.add(bloc);
    bloc.stream.listen(<span>null</span>);
    bloc.add(event);
  }
}

<span>/// Expect result:</span>
<span>/// Start picking image | Counter 1</span>
<span>/// Start picking image | Counter 2</span>
<span>/// Start picking image | Counter 3</span>
<span>///</span>
<span>/// Have picked an image | Counter 3</span>
<span>///</span>
<span>/// > Start identifying that image | Counter 3</span>
<span>/// > Finished identifying that image | Counter 3</span>
<span>///</span>
<span>/// >> Start showing result | Counter 3</span>
<span>/// >> Stopped showing result | Counter 3</span>

void main() {
  <span>final</span> bloc = <span>TestBloc</span>();
  bloc.add(<span>PickImageEvent</span>());
  bloc.add(<span>PickImageEvent</span>());
  bloc.add(<span>PickImageEvent</span>());
}
  • 1
  • Reply
Avatar
uzumaki258 Beginner
uzumaki258 Beginner
E sẽ dùng cách này, + với giảm event nội bộ. Cảm ơn a đã dành thời gian ạ.
  • 0
  • Reply