Flutter可取消未来任务插件cancelable_future的使用
Flutter可取消未来任务插件cancelable_future的使用
注意事项
切勿在生产环境中使用此插件。该代码仅用于学术研究。
什么是它?
这个插件展示了在Dart中创建可取消的Future
的可能性。虽然可以实现,但并不推荐这样做。以下是详细说明。
背景
经常遇到的任务是取消Future
的执行。然而,Future
的架构假定操作的原子性,并不支持从外部取消代码的执行。如果开发者编写了以下代码:
Future<SomeClass> someMethod() async {
...
}
他必须确保这段代码不会在任何地方被任意中断。如果开发者编写了以下代码:
await future;
他必须确保future
要么返回结果,要么抛出异常。这是Future
的架构。这可能是最佳选项之一。或许最好是在Future
的架构中直接添加一个取消方法。但是,现在要添加取消Future
的功能将引入混乱到所有现有的Dart代码中。为了向代码中添加取消功能,开发者必须同意。就像签署了一份合同。这种可能性确实存在。它是异步Stream
的生成器async*
:
Stream<SomeClass> f() async* {
try {
yield someResult;
...
yield await someFuture;
...
yield* someStream;
} finally {
..
}
}
这段代码可以在计算其值的任何yield
后被外部终止。(await someFuture
仍然是一个原子不可分割的操作。即使在yield
之前操作被取消,someFuture
也会执行其代码以完成)代码将被取消且不会继续下一步。不会有异常抛出。但是finally
块会被执行。底层看起来像这样:
Stream<SomeClass> f() async* {
try {
yield someResult;
// 1. value = someResult
// 2. if (canceled) return
// 3. send value
...
yield await someFuture;
// 1. value = await someFuture
// 2. if (canceled) return
// 3. send value
...
yield* someStream;
// 1. subscription = listen someStream
// 2. if (canceled)
// cancel subscription
// return
// 3. wait for someStream to complete
} finally {
..
}
}
因此,Dart已经提供了取消异步操作所需的一切。但是,Future
比Stream
更清晰和方便使用,尤其是在我们谈论的是单个结果而不是一系列结果时。因此出现了CancelableOperation
。“一个可以取消的异步操作”——正如文档所述。
CancelableOperation
没有命名为CancelableFuture
是为了避免混淆开发者。Future
不能被取消,而某些异步操作似乎可以。但实际上,除非在内部实现,否则没有任何异步操作可以被取消。
Future<SomeClass> f() async {
await ....;
await ....;
await ....;
await ....;
}
你可以选择不在代码执行完成并返回值(例如null
)或抛出异常之前等待代码执行,比如通过添加timeout
,从而创造一种取消异步操作的幻觉。但是指定的代码将继续运行,尽管没有人需要它的结果。它仍然会继续访问网络、解析JSON、保存值到存储中并创建其他副作用。开发者会对程序的意外行为感到惊讶,或者应用程序由于某种原因变慢。
由此得出的重要结论是Future
是原子性的,不能从外部取消。这是Dart团队的根本架构决定。没有任何外部决策可以改变这一点。有些人习惯了在这种情况下生活。有人转向async*
。有人梦想着在Dart中出现可取消的Future
。
可取消的Future
不能通过添加cancel
方法来实现。但是只有当有一种新的架构解决方案时,开发人员才能明确表示他的代码准备好被取消。例如,它可以看起来像这样(注意我发明的新关键字async cancelable
):
CancelableFuture<SomeClass> f() async cancelable {
final SomeResource resource1 = ....;
late final SomeResource resource2;
await safeAsyncCodeWithoutExceptions;
try {
resource2 = ....;
await someFuture1;
await someFuture2;
await someFuture3;
} catch (e,s) {
...
} finally {
resource2.dispose();
}
resource1.dispose();
}
在这种情况下,开发者必须意识到代码可能在await safeAsyncCodeWithoutExceptions
处被中断,而不仅仅是await someFuture1
、await someFuture2
或await someFuture3
。然后resource1
永远不会被释放,会出现资源泄漏。这是一个开发者的错误,但通过async cancelable
这句话,他签了一份合同,表明他对自己犯下的错误负责。但如果开发者编写正常的async
代码,他并没有承诺这种行为——他的代码应该肯定完成,resource1
也应该被释放。
了解有关创建可取消的Future
的问题:
https://github.com/dart-lang/sdk/issues/1806
这个包做了什么?
这是一个黑客技术,为Dart添加了一个可取消的Future
。你可以尝试使用具有可取消Future
的Future
!
这里没有async cancelable
合同,所以你现在实际上可以在任何await
处取消任何async
代码。这是通过在Zone
中可用的定时器创建钩子实现的。这并不能像yield
那样取消await
。但是你实际上可以通过取消所有创建的定时器来中止代码执行,并调用then
方法传递的Future
完成处理程序,当你写await
时隐式调用它。你可以通过传递值给onValue
处理程序来“完成”Future
,或者通过调用onError
处理程序来“抛出”异常。但是因为我们无法获取未知类T
的已知值来生成通用的Future<T>
,所以我们只能“抛出”异常AsyncCancelException
。最终,我们可以取消任何级别的异步代码嵌套中的await
,但是原始的CancelableFuture
本身会在取消时抛出异常。如果你不需要异常,可以使用orNull
属性或onCancel
方法。
即使你最终决定在工作项目中使用这个包(我不建议这样做,因为它破坏了await
逻辑),记住你只能在你的async
代码中使用自己的async
方法。第三方async
方法你无法控制,它们不知道你的实验,当你取消它们时,它们无法释放它们使用的资源。
仅用于学术目的!
使用方法
Future<void> someOperation(int i) async {
try {
print('operation $i 0%');
await Future<void>.delayed(const Duration(milliseconds: 100));
print('operation $i 25%');
await Future<void>.delayed(const Duration(milliseconds: 100));
print('operation $i 50%');
await Future<void>.delayed(const Duration(milliseconds: 100));
print('operation $i 75%');
await Future<void>.delayed(const Duration(milliseconds: 100));
print('operation $i 100%');
} finally {
print('operation $i finally');
}
}
Future<void> someLongOperation() async {
try {
print('operation 1');
await someOperation(1);
print('operation 2');
await someOperation(2);
print('operation 3');
await someOperation(3);
print('operation 4');
await someOperation(4);
} finally {
print('operations finally');
}
}
final f1 = CancelableFuture(() async {
try {
await someLongOperation();
return 'result';
} finally {
print('main finally');
}
});
final cancelfuture = Future<void>.delayed(
const Duration(milliseconds: 650),
() async {
print('--- cancel ---');
await f1.cancel();
print('--- really canceled ---');
},
);
print('result: ${await f1.orNull}');
print('result: ${await f1.onCancel(() => 'canceled')}');
try {
print(await f1);
} on AsyncCancelException catch (error, stackTrace) {
print('exception: [${error.runtimeType}] $error');
if (printStackTrace) {
print(Chain.forTrace(stackTrace).terse);
}
}
await cancelfuture;
输出结果如下:
operation 1
operation 1 0%
operation 1 25%
operation 1 50%
operation 1 75%
--- cancel ---
operation 1 100%
operation 1 finally
operation 2
operation 2 0%
operation 2 finally
operations finally
main finally
result: null
result: canceled
exception: [AsyncCancelException] Async operation canceled
--- really canceled ---
更多关于Flutter可取消未来任务插件cancelable_future的使用的实战教程也可以访问 https://www.itying.com/category-92-b0.html
更多关于Flutter可取消未来任务插件cancelable_future的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html
在Flutter中,cancelable_future
是一个用于创建可取消未来任务(Future)的插件。它允许你在任务执行过程中根据需求取消该任务,从而避免不必要的资源消耗或处理逻辑。下面是一个关于如何使用 cancelable_future
插件的示例代码。
首先,你需要在你的 pubspec.yaml
文件中添加该插件的依赖:
dependencies:
flutter:
sdk: flutter
cancelable_future: ^x.y.z # 替换为最新版本号
然后,运行 flutter pub get
来安装依赖。
接下来是一个完整的 Flutter 应用程序示例,展示了如何使用 cancelable_future
来创建和取消一个未来任务:
import 'package:flutter/material.dart';
import 'package:cancelable_future/cancelable_future.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Cancelable Future Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: CancelableFutureDemo(),
);
}
}
class CancelableFutureDemo extends StatefulWidget {
@override
_CancelableFutureDemoState createState() => _CancelableFutureDemoState();
}
class _CancelableFutureDemoState extends State<CancelableFutureDemo> {
bool _isRunning = false;
bool _isCanceled = false;
String _result = '';
void _startTask() async {
setState(() {
_isRunning = true;
_isCanceled = false;
_result = '';
});
final cancelableFuture = CancelableFuture.run(() async {
for (int i = 0; i < 10; i++) {
await Future.delayed(Duration(seconds: 1));
print('Task progress: $i');
// 检查任务是否被取消
if (cancelableFuture.isCanceled) {
print('Task was canceled');
return;
}
}
print('Task completed');
return 'Task Success';
});
cancelableFuture.result.then((value) {
if (!mounted) return;
setState(() {
_isRunning = false;
_result = value ?? 'Task Failed';
});
}).catchError((error) {
if (!mounted) return;
setState(() {
_isRunning = false;
_result = 'Task Error: $error';
});
});
// 持有对取消函数的引用,以便后续调用
final cancelFunction = cancelableFuture.cancel;
// 提供一个按钮来取消任务(在实际应用中,你可能会根据其他条件调用这个取消函数)
ElevatedButton(
onPressed: () {
if (_isRunning) {
cancelFunction();
setState(() {
_isCanceled = true;
});
}
},
child: Text('Cancel Task'),
).showDialog(context: context);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Cancelable Future Demo'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
Text(
_isRunning ? (_isCanceled ? 'Task is being canceled...' : 'Task is running...') : 'Task is not running',
style: TextStyle(fontSize: 20),
),
SizedBox(height: 20),
Text(
_result,
style: TextStyle(fontSize: 18),
),
SizedBox(height: 20),
ElevatedButton(
onPressed: _startTask,
child: Text('Start Task'),
),
],
),
),
);
}
}
在这个示例中,我们创建了一个简单的 Flutter 应用程序,它包含一个按钮来启动一个模拟任务。这个任务每秒打印一次进度,并在10秒后完成。我们还提供了一个取消按钮,可以在任务执行期间取消该任务。
关键点是:
- 使用
CancelableFuture.run
来创建一个可取消的未来任务。 - 在任务执行过程中定期检查
cancelableFuture.isCanceled
来确定任务是否被取消。 - 持有对取消函数的引用,并在需要时调用它来取消任务。
这个示例展示了如何使用 cancelable_future
插件来管理可取消的未来任务,从而增强你的 Flutter 应用程序的响应性和用户体验。