Dart:如何防止异步函数的并发调用

huangapple go评论61阅读模式
英文:

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&lt;void&gt; add100elements() async {
      // this functions queries a database and adds 100 elements from the db to a local list
       var elementsToAdd = await db.rawQuery(&#39;select * from quotes where id &gt; ? limit 100&#39;, 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&lt;void&gt; 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(&#39;select * from quotes where id &gt; ? limit 100&#39;, 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&lt;void&gt; 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(&#39;select * from quotes where id &gt; ? limit 100&#39;, 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 &#39;dart:async&#39;;

class OneAtATime {
  var _isRunning = false;
  
  Future&lt;void&gt; runOrSkip(FutureOr&lt;void&gt; Function() block) async {
    if (_isRunning) return;
    _isRunning = true;
    try {
      await block();
    }
    finally {
      _isRunning = false;
    }
  }
}

OneAtATime runner;

Future&lt;void&gt; add100elements() {
  return runner.runOrSkip(() async {
    var elementsToAdd = await db.rawQuery(&#39;select * from quotes where id &gt; ? limit 100&#39;, this.currentId);
    this.localList.addAll(elementsToAdd);
    this.currentId += 100;
  });
}

huangapple
  • 本文由 发表于 2023年3月3日 22:59:13
  • 转载请务必保留本文链接:https://go.coder-hub.com/75628641.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定