【问题标题】:is there any way to cancel a dart Future?有没有办法取消飞镖未来?
【发布时间】:2013-07-09 15:53:59
【问题描述】:

在 Dart UI 中,我有一个 [提交] 按钮来启动一个长的异步请求。 [submit] 处理程序返回一个 Future。接下来,将按钮 [提交] 替换为按钮 [取消] 以允许取消整个操作。在 [cancel] 处理程序中,我想取消长操作。如何取消提交处理程序返回的 Future?我没有找到这样做的方法。

【问题讨论】:

  • 答案很好。仅用于上下文:您不能取消 future。期货不是操作,它们是代表操作结果的对象。没有内置的方法来告诉未来告诉底层操作停止。这就是为什么这里所有的解决方案都是使用Future 以外的东西。这也是 Dart 中的 future 可以共享​​>的原因。如果有人可以为其他人取消未来,那么您就必须更加谨慎地分享未来。

标签: dart dart-async


【解决方案1】:

您可以使用CancelableOperationCancelableCompleter 取消未来。请参阅下面的 2 个版本:

解决方案 1:CancelableOperation(包含在测试中,您可以自己尝试):

  • 取消未来

test("CancelableOperation with future", () async {

  var cancellableOperation = CancelableOperation.fromFuture(
    Future.value('future result'),
    onCancel: () => {debugPrint('onCancel')},
  );

// cancellableOperation.cancel();  // uncomment this to test cancellation

  cancellableOperation.value.then((value) => {
    debugPrint('then: $value'),
  });
  cancellableOperation.value.whenComplete(() => {
    debugPrint('onDone'),
  });
});
  • 取消直播

test("CancelableOperation with stream", () async {

  var cancellableOperation = CancelableOperation.fromFuture(
    Future.value('future result'),
    onCancel: () => {debugPrint('onCancel')},
  );

  //  cancellableOperation.cancel();  // uncomment this to test cancellation

  cancellableOperation.asStream().listen(
        (value) => { debugPrint('value: $value') },
    onDone: () => { debugPrint('onDone') },
  );
});

以上两个测试都会输出:

then: future result
onDone

现在,如果我们取消注释 cancellableOperation.cancel();,那么上述两个测试都会输出:

onCancel

解决方案 2:CancelableCompleter(如果您需要更多控制)

test("CancelableCompleter is cancelled", () async {

  CancelableCompleter completer = CancelableCompleter(onCancel: () {
    print('onCancel');
  });

  // completer.operation.cancel();  // uncomment this to test cancellation

  completer.complete(Future.value('future result'));
  print('isCanceled: ${completer.isCanceled}');
  print('isCompleted: ${completer.isCompleted}');
  completer.operation.value.then((value) => {
    print('then: $value'),
  });
  completer.operation.value.whenComplete(() => {
    print('onDone'),
  });
});

输出:

isCanceled: false
isCompleted: true
then: future result
onDone

现在,如果我们取消注释 cancellableOperation.cancel();,我们会得到输出:

onCancel
isCanceled: true
isCompleted: true

请注意,如果您使用await cancellableOperation.valueawait completer.operation,那么future 将永远不会返回结果,并且如果操作被取消,它将无限期地等待。这是因为await cancellableOperation.value 与写cancellableOperation.value.then(...) 相同,但如果操作被取消,则永远不会调用then()

记得添加async Dart 包。

Code gist

【讨论】:

  • 感谢使用标准 dart 库,而不是自己发明轮子。
  • 这不是标准的飞镖库。这是一个外部依赖。
  • 关于异步的好处。这非常令人困惑,因为还有一个名为 async 的本机包
  • 请注意,这不适用于延迟期货。这些是基于Timer 的,内部定时器引用没有保存,所以一旦回调开始就没有办法阻止它的执行。
  • 这个答案具有误导性。这些类不会取消未来本身,而是创建可用于模仿取消行为的包装器
【解决方案2】:

如何取消Future.delayed

一个简单的方法是使用Timer :)

Timer _timer;

void _schedule() {
  _timer = Timer(Duration(seconds: 2), () { 
    print('Do something after delay');
  });
}

@override
void dispose() {
  super.dispose();
  _timer?.cancel();
}

【讨论】:

  • 但是你不能在Timerawait :(
  • 如何在手势检测器 ontap 上调用 cancel_timer?
  • @Texv _timer?.cancel();
  • @AndreyGordeev 没关系,我还有别的想法。 _timer?.cancel() 在持续时间计时器结束后取消该函数的运行。相反,我想取消持续时间(跳转到 0 秒)以更快地执行该功能
  • @iDecode 你可以让Timer完成一个Completer,调用者可以awaitCompleterFuture
【解决方案3】:

据我所知,没有办法取消 Future。但是有一种方法可以取消 Stream 订阅,也许对您有帮助。

在按钮上调用onSubmit 会返回一个StreamSubscription 对象。您可以显式存储该对象,然后在其上调用 cancel() 以取消流订阅:

StreamSubscription subscription = someDOMElement.onSubmit.listen((data) {

   // you code here

   if (someCondition == true) {
     subscription.cancel();
   }
});

稍后,作为对某些用户操作的响应,您或许可以取消订阅:

【讨论】:

  • 我听从了你的想法。它运作良好。事情如下。 DAO 层返回 1000 个随机数,每个随机数由远程服务器通过 1000 个 HTTP 请求生成。 DAO 层返回一个响应,包括 2 个列表:一个 1000 个 Future 列表和一个链接到 1000 个 HTTP 请求的 1000 个 StreamSubscription 列表。我使用期货在 UI 中显示随机数,并使用 StreamSubscriptions 取消 HTTP 请求。谢谢!
  • 另外,你真的需要 1000 个 HTTP 请求吗?有什么理由不能只使用 Math.Random 生成 1000 个随机数?还是只有一个 HTTP 请求就可以获取所有数字?
  • 我只是想测试一个Future列表的取消。这就是我发现做这个测试的方式。是的,我接受答案。
【解决方案4】:

对于那些试图在 Flutter 中实现这一点的人,这里是一个简单的例子。

class MyPage extends StatelessWidget {
  final CancelableCompleter<bool> _completer = CancelableCompleter(onCancel: () => false);

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("Future")),
      body: Column(
        children: <Widget>[
          RaisedButton(
            child: Text("Submit"),
            onPressed: () async {
              // it is true only if the future got completed
              bool _isFutureCompleted = await _submit();
            },
          ),
          RaisedButton(child: Text("Cancel"), onPressed: _cancel),
        ],
      ),
    );
  }

  Future<bool> _submit() async {
    _completer.complete(Future.value(_solve()));
    return _completer.operation.value;
  }

  // This is just a simple method that will finish the future in 5 seconds
  Future<bool> _solve() async {
    return await Future.delayed(Duration(seconds: 5), () => true);
  }

  void _cancel() async {
    var value = await _completer.operation.cancel();
    // if we stopped the future, we get false
    assert(value == false);
  }
}

【讨论】:

    【解决方案5】:

    我完成“取消”计划执行的一种方法是使用Timer。在这种情况下,我实际上推迟了它。 :)

    Timer _runJustOnceAtTheEnd;
    
    void runMultipleTimes() {
      _runJustOnceAtTheEnd?.cancel();
      _runJustOnceAtTheEnd = null;
    
      // do your processing
    
      _runJustOnceAtTheEnd = Timer(Duration(seconds: 1), onceAtTheEndOfTheBatch);
    }
    
    void onceAtTheEndOfTheBatch() {
      print("just once at the end of a batch!");
    }
    
    
    runMultipleTimes();
    runMultipleTimes();
    runMultipleTimes();
    runMultipleTimes();
    
    // will print 'just once at the end of a batch' one second after last execution
    

    runMultipleTimes() 方法会被依次调用多次,但只有在批处理的 1 秒后,onceAtTheEndOfTheBatch 才会被执行。

    【讨论】:

    • OP 询问的是取消 Future,而不是执行。
    【解决方案6】:

    我的 2 美分值...

    class CancelableFuture {
      bool cancelled = false;
      CancelableFuture(Duration duration, void Function() callback) {
        Future<void>.delayed(duration, () {
          if (!cancelled) {
            callback();
          }
        });
      }
    
      void cancel() {
        cancelled = true;
      }
    }
    

    【讨论】:

    • 在接受的答案中查看 CancelableOperation。
    • @JonScalet 我不认为CancelableOperation 会阻止未来的回调执行,而答案会。尝试取消Future.delayed(...),它仍然会执行---CancelableOperation.fromFuture(Future.delayed(Duration(seconds: 1), () =&gt; print("Finished 1")), onCancel: () =&gt; print("Cancelled"),).cancel()
    • @geg 没错,它实际上并没有取消 Future 的计算/执行。
    • 很好......但它不是顾名思义的未来,所以不能等待。伤心,因为能够等待Future 是我使用Future 而不是Timer 的主要原因之一。
    • 对于可以等待的可取消延迟未来,请查看我的回答 (CancelableCompleter)
    【解决方案7】:

    pub.dev 上的async package 中有一个CancelableOperation,您现在可以使用它来执行此操作。不要将此包与没有此类的内置 dart 核心库 dart:async 混淆。

    【讨论】:

      【解决方案8】:

      将未来的任务从“做某事”改为“做某事,除非它被取消”。一个明显的实现方法是设置一个布尔标志,并在开始处理之前在未来的关闭中检查它,也许在处理期间的几个点。

      此外,这似乎有点小技巧,但是将未来的超时设置为零似乎可以有效地取消未来。

      【讨论】:

      • 似乎将未来的超时设置为零并不会取消未来。下面是证明这一点的代码和输出。 Future f = new Future.delayed(new Duration(seconds: 2), () =&gt; print('2 secs passed')); f.timeout(const Duration(seconds: 0), onTimeout: () { print ('timed out'); }); 打印 timed out 2 secs passed 。使用 catchError 捕获引发的超时,或提供 onTimeout 参数并没有什么不同。未来总是在奔跑。
      【解决方案9】:

      以下代码有助于设计未来的超时功能,可以手动取消。

      import 'dart:async';
      
      class API {
        Completer<bool> _completer;
        Timer _timer;
      
        // This function returns 'true' only if timeout >= 5 and
        // when cancelOperation() function is not called after this function call.
        //
        // Returns false otherwise
        Future<bool> apiFunctionWithTimeout() async {
          _completer = Completer<bool>();
          // timeout > time taken to complete _timeConsumingOperation() (5 seconds)
          const timeout = 6;
      
          // timeout < time taken to complete _timeConsumingOperation() (5 seconds)
          // const timeout = 4;
      
          _timeConsumingOperation().then((response) {
            if (_completer.isCompleted == false) {
              _timer?.cancel();
              _completer.complete(response);
            }
          });
      
          _timer = Timer(Duration(seconds: timeout), () {
            if (_completer.isCompleted == false) {
              _completer.complete(false);
            }
          });
      
          return _completer.future;
        }
      
        void cancelOperation() {
          _timer?.cancel();
          if (_completer.isCompleted == false) {
            _completer.complete(false);
          }
        }
      
        // this can be an HTTP call.
        Future<bool> _timeConsumingOperation() async {
          return await Future.delayed(Duration(seconds: 5), () => true);
        }
      }
      
      void main() async {
        API api = API();
        api.apiFunctionWithTimeout().then((response) {
          // prints 'true' if the function is not timed out or canceled, otherwise it prints false
          print(response);
        });
        // manual cancellation. Uncomment the below line to cancel the operation.
        //api.cancelOperation();
      }
      

      返回类型可以从bool 更改为您自己的数据类型。 Completer 对象也应相应更改。

      【讨论】:

        【解决方案10】:

        一个从未来取消注册回调的小类。此类不会阻止执行,但当您需要切换到具有相同类型的另一个未来时可以提供帮助。不幸的是我没有测试它,但是:

        class CancelableFuture<T> {
          Function(Object) onErrorCallback;
          Function(T) onSuccessCallback;
          bool _wasCancelled = false;
        
          CancelableFuture(Future<T> future,
              {this.onSuccessCallback, this.onErrorCallback}) {
            assert(onSuccessCallback != null || onErrorCallback != null);
            future.then((value) {
              if (!_wasCancelled && onSuccessCallback != null) {
                onSuccessCallback(value);
              }
            }, onError: (e) {
              if (!_wasCancelled && onErrorCallback != null) {
                onErrorCallback(e);
              }
            });
          }
        
          cancel() {
            _wasCancelled = true;
          }
        }
        

        这里是使用示例。附言我在我的项目中使用提供者:

        _fetchPlannedLists() async {
            if (_plannedListsResponse?.status != Status.LOADING) {
              _plannedListsResponse = ApiResponse.loading();
              notifyListeners();
            }
        
            _plannedListCancellable?.cancel();
        
            _plannedListCancellable = CancelableFuture<List<PlannedList>>(
                _plannedListRepository.fetchPlannedLists(),
                onSuccessCallback: (plannedLists) {
              _plannedListsResponse = ApiResponse.completed(plannedLists);
              notifyListeners();
            }, onErrorCallback: (e) {
              print('Planned list provider error: $e');
              _plannedListsResponse = ApiResponse.error(e);
              notifyListeners();
            });
          }
        

        您可以在以下情况下使用它,当语言发生变化并提出请求时,您不关心先前的响应并提出另一个请求! 此外,我真的很想知道这个功能不是来自盒子。

        【讨论】:

          【解决方案11】:

          这是取消等待延迟的未来的解决方案

          此解决方案类似于可等待的Timer可取消的Future.delayed:它可以像Timer 一样可取消,也可以像Future 一样可等待。 p>

          它基于一个非常简单的类CancelableCompleter,这是一个演示:

          import 'dart:async';
          
          void main() async {  
            print('start');
            
            // Create a completer that completes after 2 seconds…
            final completer = CancelableCompleter.auto(Duration(seconds: 2));
            
            // … but schedule the cancelation after 1 second
            Future.delayed(Duration(seconds: 1), completer.cancel);
            
            // We want to await the result
            final result = await completer.future;
          
            print(result ? 'completed' : 'canceled');
            print('done');
            // OUTPUT:
            //  start
            //  canceled
            //  done
          }
          

          现在是类的代码:

          class CancelableCompleter {
            CancelableCompleter.auto(Duration delay) : _completer = Completer() {
              _timer = Timer(delay, _complete);
            }
          
            final Completer<bool> _completer;
            late final Timer? _timer;
          
            bool _isCompleted = false;
            bool _isCanceled = false;
          
            Future<bool> get future => _completer.future;
          
            void cancel() {
              if (!_isCompleted && !_isCanceled) {
                _timer?.cancel();
                _isCanceled = true;
                _completer.complete(false);
              }
            }
          
            void _complete() {
              if (!_isCompleted && !_isCanceled) {
                _isCompleted = true;
                _completer.complete(true);
              }
            }
          }
          

          this DartPad 中提供了具有更完整类的运行示例。

          【讨论】:

            猜你喜欢
            • 2021-10-03
            • 2016-04-13
            • 1970-01-01
            • 1970-01-01
            • 2019-10-28
            • 2019-03-11
            • 1970-01-01
            • 1970-01-01
            • 2012-08-22
            相关资源
            最近更新 更多