Dart:如何在异步函数

时间:2017-02-06 15:10:44

标签: async-await dart

我非常喜欢Dart中的async / await模式。 它允许我编写可读的方法。

但是,有一些事情是有问题的,尤其是一些事情,我根本不知道管理的热情。

问题是在方法中使用异步和多次等待,我们在方法中引入了并发性。 例如,如果我有一个方法:

Future<int> foo(int value) async {
await foo2();
await foo3();
await foo4();
int ret = foo5(value);
return ret;
}

嗯,这是一个非常简单的例子。 这里的问题是,对于每个await,该方法都放在事件循环中。 没关系,当你理解它时,但这并不妨碍你的应用程序再次调用方法,因为它已经重新调整了一个值。

考虑该方法是否正在管理类实例所共有的数据,而不是方法本身。

所以,我尝试了以下解决方案:

bool isWorking = false;

Future<int> foo(int value) async {
if (isWorking) return foo(value);
isWorking = true;

await foo2();
await foo3();
await foo4();
int ret = foo5(value);

isWorking = False;

return ret;
}

据我所知,调用future方法会立即将它放在事件循环中,所以我认为方法的并发调用的执行被推迟到第一个结束时。 但不是那样,程序进入无限循环。

任何人都可以给我一个解释和解决这个问题的方法吗?

编辑: 总的来说,我认为像其他语言一样,拥有一个synchronized关键字,意味着该方法,如果第二次调用将等到第一次结束,这可能会很有趣。 类似的东西:

Future<int> foo(int value) async synchronized {

编辑2:

我真的很兴奋,因为我觉得我已经解决了这个问题,我已经有很长一段时间了。感谢Argenti,尤其是Alexandre给我的解决方案。我只是简单地重新构建了解决方案以便于重用(至少对我来说),我在这里发布了我创建的类,以及如何将它用于可能需要它的人的示例(尝试自担风险;-))。 我使用了mixin,因为我发现它很实用,但如果你愿意,可以单独使用Locker类。

myClass extends Object with LockManager {

  Locker locker = LockManager.getLocker();

  Future<int> foo(int value) async {

   _recall() {
      return foo(value);
   } 

   if (locker.locked) {
     return await locker.waitLock();
   }
   locker.setFunction(_recall);
   locker.lock();

   await foo2();
   await foo3();
   await foo4();
   int ret = foo5(value);

   locker.unlock();

   return ret;
  }
}

课程是:

import 'dart:async';

class LockManager {

static Locker getLocker() => new Locker();

}

class Locker {

  Future<Null> _isWorking = null;
  Completer<Null> completer;
  Function _function;
  bool get locked => _isWorking != null;

  lock() {
    completer = new Completer();
    _isWorking = completer.future;
  }

  unlock() {
    completer.complete();
    _isWorking = null;
  }

  waitLock() async {
      await _isWorking;
      return _function();
  }

  setFunction(Function fun) {
    if (_function == null) _function = fun;
  }

}

我已经以这种方式构建了代码,以便您可以在类中的多个方法中轻松使用它。在这种情况下,每个方法需要一个Locker实例。 我希望它有用。

5 个答案:

答案 0 :(得分:4)

答案很好,这里只是&#34; mutex&#34;的另一个实现。这可以防止异步操作被交错。

class AsyncMutex {
  Future _next = new Future.value(null);
  /// Request [operation] to be run exclusively.
  ///
  /// Waits for all previously requested operations to complete,
  /// then runs the operation and completes the returned future with the
  /// result.
  Future<T> run<T>(Future<T> operation()) {
    var completer = new Completer<T>();
    _next.whenComplete(() {
      completer.complete(new Future<T>.sync(operation));
    });
    return _next = completer.future;
  }
}

它没有很多功能,但它很简短,希望可以理解。

答案 1 :(得分:3)

您可以使用FutureCompleter代替布尔值来实现您的目标:

Future<Null> isWorking = null;

Future<int> foo(int value) async {
  if (isWorking != null) {
    await isWorking; // wait for future complete
    return foo(value);
  }

  // lock
  var completer = new Completer<Null>();
  isWorking = completer.future;

  await foo2();
  await foo3();
  await foo4();
  int ret = foo5(value);

  // unlock
  completer.complete();
  isWorking = null;

  return ret;
}

第一次调用isWorking方法为null时,不会进入if部分并创建isWorking作为将在最后完成的未来方法。如果在第一次通话完成未来foo之前对isWorking进行了其他通话,则此通话会进入if部分,并等待未来isWorking完成。对于在第一次呼叫完成之前可以完成的所有呼叫,这是相同的。第一次通话完成后(isWorking设置为null),等待来电通知他们将再次拨打foo。其中一个将作为第一个电话进入foo,并且将完成相同的工作流程。

请参阅https://dartpad.dartlang.org/dceafcb4e6349acf770b67c0e816e9a7以更好地查看工作流程。

答案 2 :(得分:2)

我想真正需要的是一个实现并发管理原语的Dart库,例如锁,互斥锁和信号量。

我最近使用Pool程序包有效地实现了一个互斥锁,以防止&#39;并发&#39;访问资源。 (这是&#39;扔掉代码,所以请不要把它作为高质量的解决方案。)

稍微简化示例:

final Pool pool = new Pool(1, timeout: new Duration(...));

Future<Null> foo(thing, ...) async {
  PoolResource rp = await pool.request();

  await foo1();
  await foo2();
  ...

  rp.release();
}

在调用foo()中的异步函数之前从池中请求资源可以确保当多个并发调用太foo()时,调用foo1()和foo2()不会得到&#39;交织&#39;不当。

编辑:

似乎有几个软件包可以解决提供互斥的问题:https://www.google.com/search?q=dart+pub+mutex

答案 3 :(得分:0)

异步和并发是两个独立的主题。上面的代码没有任何并发​​,它都是串行执行的。您正在使用的代码继续插入更多foo(value)事件,而isWorking是真的 - 它永远不会完成。

您正在寻找的工具是Completer<T>。有问题的特定代码片段难以辨别,因此我将举一个例子。假设您拥有共享数据库连接的代码。打开数据库连接是一种异步操作。当一个方法要求数据库连接时,它等待open打开完成。在等待期间,另一种方法要求数据库连接。所需的结果是只打开一个数据库连接,并且向两个调用者返回一个数据库连接:

Connection connection;
Completer<Connection> connectionCompleter;
bool get isConnecting => connectionCompleter != null;

Future<Connection> getDatabaseConnection() {
  if (isConnecting) {
    return connectionCompleter.future;
  } 

  connectionCompleter = new Completer<Connection();

  connect().then((c) {
    connection = c;
    connectionCompleter.complete(c);
    connectionCompleter = null;
  });

  return connectionCompleter.future;
}

方法第一次调用getDatabaseConnection时,调用connect函数,并将完成者的未来返回给调用者。假设在getDatabaseConnection完成之前再次调用connect,将返回相同的完成者的未来,但不会再次调用connect

connect完成时,完成者的事件将添加到事件队列中。这将触发从getDatabaseConnection返回的两个期货按顺序完成 - connection将有效。

请注意,getDatabaseConnection甚至不是异步方法,只返回Future。它可能是异步的,但它只会浪费CPU周期并使你的调用堆栈变得丑陋。

答案 4 :(得分:0)

现在是 2021 年,我们可以使用 synchronized 3.0.0