英文:
Dart : how to prevent concurrent calls to async function
问题
在Flutter中,我有一个异步函数,不应该同时被调用两次。
这里是一个简单的示例:
class DatabaseHelper {
Future<void> add100elements() async {
// 这个函数查询数据库,并将数据库中的100个元素添加到本地列表中
var elementsToAdd = await db.rawQuery('select * from quotes where id > ? limit 100', this.currentId);
this.localList.addAll(elementsToAdd);
this.currentId += 100;
}
}
可能会有两个不同的地方在应用程序中几乎同时调用add100elements
。这意味着代码将被执行两次,并添加相同的100个元素。我希望通过阻止add100elements
函数被调用两次来避免这种情况。我可以简单地这样做:
class DatabaseHelper {
bool isRunning = false;
Future<void> add100elements() async {
// 这个函数查询数据库并将数据库中的100个元素添加到本地列表中
if (isRunning) return;
isRunning = true;
var elementsToAdd = await db.rawQuery('select * from quotes where id > ? limit 100', this.currentId);
this.localList.addAll(elementsToAdd);
this.currentId += 100;
isRunning = false;
}
}
但这感觉有点手动和笨拙。在Dart中是否有内置的方法来实现这一点?或者有更好的方法吗?
英文:
In Flutter, I have an async function that should not be called twice at the same time.
Here's a minimal example
class DatabaseHelper {
Future<void> add100elements() async {
// this functions queries a database and adds 100 elements from the db to a local list
var elementsToAdd = await db.rawQuery('select * from quotes where id > ? limit 100', this.currentId);
this.localList.addAll(elementsToAdd);
this.currentId += 100;
}
}
There could be two different places in the app that call add100elements
at almost the same twice. That means the code will be executed twice, and add the same 100 elements.
I want to avoid this, by preventing the add100elements
function to be called twice.
I could simply do this:
class DatabaseHelper {
bool isRunning = false;
Future<void> add100elements() async {
// this functions queries a database and adds 100 elements from the db to a local list
if (isRunning) return;
isRunning = true;
var elementsToAdd = await db.rawQuery('select * from quotes where id > ? limit 100', this.currentId);
this.localList.addAll(elementsToAdd);
this.currentId += 100;
isRunning = false;
}
}
But that feels a little manual and clumsy.
Is there a built-in way to do this in dart? Or a better way?
答案1
得分: 2
以下是翻译好的内容:
Pool
您可以考虑使用 Pool 类。当您希望限制并发操作的数量到某个值 N 时,这是有用的,但在您的用例中 N = 1 时可能有点过度。
使用 Pool
与您的基于标志的方法不同,尝试并发运行将在正在进行的操作完成后运行,而不是跳过。
Flag
您的 isRunner
标志实现很好,但可以使用 try
/finally
来使其更加健壮,如下所示:
Future<void> add100elements() async {
// 此函数查询数据库并将数据库中的 100 个元素添加到本地列表
if (isRunning) return;
isRunning = true;
try {
var elementsToAdd = await db.rawQuery('select * from quotes where id > ? limit 100', this.currentId);
this.localList.addAll(elementsToAdd);
this.currentId += 100;
}
finally {
isRunning = false;
}
}
此修改的优点是,即使发生异常,标志也会被清除。否则,异常会使标志永久处于 true
状态。
如果需要,您还可以进一步抽象这个模式:
import 'dart:async';
class OneAtATime {
var _isRunning = false;
Future<void> runOrSkip(FutureOr<void> Function() block) async {
if (_isRunning) return;
_isRunning = true;
try {
await block();
}
finally {
_isRunning = false;
}
}
}
OneAtATime runner;
Future<void> add100elements() {
return runner.runOrSkip(() async {
var elementsToAdd = await db.rawQuery('select * from quotes where id > ? limit 100', this.currentId);
this.localList.addAll(elementsToAdd);
this.currentId += 100;
});
}
英文:
There have been several valid and useful suggestions in the comments. And I can add a couple things.
Pool
You could consider using the Pool class. This is useful when you want to limit the number of concurrent operations to some value N, but is perhaps overkill when N = 1 as in your use case.
Using Pool
would differ from your flag-based approach in that an attempted concurrent run would run after the in-progress operation completes, rather than be skipped.
Flag
Your isRunner
flag implementation is just fine, but can be made more robust using try
/finally
, as in:
Future<void> add100elements() async {
// this functions queries a database and adds 100 elements from the db to a local list
if (isRunning) return;
isRunning = true;
try {
var elementsToAdd = await db.rawQuery('select * from quotes where id > ? limit 100', this.currentId);
this.localList.addAll(elementsToAdd);
this.currentId += 100;
}
finally {
isRunning = false;
}
}
This modification offers the advantage that the flag will be cleared even in the event of an exception. Otherwise, an exception would leave the flag permanently "stuck" in the true
state.
You can go one step further if desired and abstract this if it's a common pattern:
import 'dart:async';
class OneAtATime {
var _isRunning = false;
Future<void> runOrSkip(FutureOr<void> Function() block) async {
if (_isRunning) return;
_isRunning = true;
try {
await block();
}
finally {
_isRunning = false;
}
}
}
OneAtATime runner;
Future<void> add100elements() {
return runner.runOrSkip(() async {
var elementsToAdd = await db.rawQuery('select * from quotes where id > ? limit 100', this.currentId);
this.localList.addAll(elementsToAdd);
this.currentId += 100;
});
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论