Dart-使长时间运行的同步功能异步

时间:2020-05-11 18:51:38

标签: dart

我有一个函数,可能需要花费几秒钟来执行,并且它是同步的。是否:

String slowFunction() { ... }
...
Future<String>(() => slowFunction());

将其更改为异步吗?

如果我下一步需要它的结果,那么这段代码有意义吗?

Future<void> anotherFunction() async {
  // other async calls with Futures and await
  ...
  final result = await Future<String>(() => slowFunction());
  print(result);
  ...
  // do something else with result
}

创建Future只是立即在其上await似乎有点奇怪。我应该只调用该函数吗?我猜这有点“屈服”,它允许之前执行其他代码,但是这样的代码有什么用吗?

3 个答案:

答案 0 :(得分:4)

制作一个本质上是同步的进程并将其打扮成异步进程没有意义。这是由于不仅在Dart中,而且在一般情况下,异步性(更通常称为“并发”)如何工作。并发只是一种编程技巧,它使多个操作在同一线程中相互交错运行,从而产生了真正的并行性(即不同线程或进程同时运行的地方)的错觉。这样一来,通常在等待资源释放的过程中通常会阻塞,直到以后程序继续执行其他操作为止。

如果您要执行一个同步流程,该流程由于正在积极完成工作而阻塞了 ,则该程序无论如何都会像执行“异步”代码那样阻塞,否则该程序将封锁时间一样长,但稍后。无论哪种方式,您仍然需要长时间运行才能阻止程序。

以您要问的以下示例为例:进行长时间运行的过程并将其包装在Future中,从而使其成为“异步”:

String slowFunction() { ... }
...
String result = await Future(slowFunction);

在正常的并发中,这会将slowFunction放入异步队列。下次程序出现一些停机时间时(例如,在两次UI绘制调用之间),它将将该函数从队列中拉出并进行处理。然后 thats 该函数执行时将阻塞2-3秒。

在Dart中,它的工作方式略有不同。由于slowFunction不是async函数,并且没有await任何东西,因此Dart无论如何都会尝试同步运行它,在这种情况下,您不必费心将其包装在{{ 1}}。

如果要中断同步功能的操作,则有两个选项。您必须将其分解成可以在Future之间进行操作的不同操作(这本身是一个复杂的过程,并不总是可能的,并且通常是很好的代码源),或者您可以卸载该函数完全采用单独的线程,采用 parallelism 而非单纯的 concurrency

Dart是单线程的,但是可以通过使用隔离进行多进程处理。 (隔离是Dart子进程的名称,与Dart中可以使用的真正多线程非常接近。)通过将函数包装在await中,可以在完全独立的进程上运行工作。这样,如果该过程阻塞2-3秒,则完全不会影响您的应用程序的大部分。

但是有一个陷阱。由于隔离是完全不同的过程,因此不会共享任何内存。这意味着隔离区有权访问的任何数据都必须通过使用“端口”(即IsolateSendPort)手动传递。这自然会使隔离编程有些痛苦,但是作为交换,您不会遇到诸如程序出现竞争状况或陷入僵局之类的事情。 (至少由于共享内存问题。严格来说,还有很多其他方法可以获取死锁和争用条件。)

使用ReceivePort的方式如下:

Isolate

答案 1 :(得分:2)

我有一个函数,可能需要花费几秒钟来执行,并且它是同步的。是否:

String slowFunction() { ... }
...
Future<String>(() => slowFunction());

将其更改为异步吗?

仅返回Future不会使您的函数以您可能想要的方式异步。

Dart隔离是单线程的。如果希望其他工作能够与长时间运行的操作同时发生,则slowFunction在内部需要使用await(这是用于创建Future.then()回调的语法糖)以允许执行产生。

考虑以下代码:

Future<void> longRunningOperation1() async {
  for (var i = 0; i < 100000000; i += 1) {
    if (i % 10000000 == 0) {
      print('longRunningOperation1: $i');
    }
  }
}

Future<void> longRunningOperation2() async {
  for (var i = 0; i < 100000000; i += 1) {
    if (i % 10000000 == 0) {
      print('longRunningOperation2: $i');
    }
  }
}

Future<void> main() async {
  await Future.wait([longRunningOperation1(), longRunningOperation2()]);
}

您将看到longRunningOperation1longRunningOperation2从不重叠;一个总是在另一个开始之前运行完成。要允许操作以最小的更改重叠,可以执行以下操作:

Future<void> longRunningOperation1() async {
  for (var i = 0; i < 100000000; i += 1) {
    if (i % 10000000 == 0) {
      print('longRunningOperation1: $i');
      await null;
    }
  }
}

Future<void> longRunningOperation2() async {
  for (var i = 0; i < 100000000; i += 1) {
    if (i % 10000000 == 0) {
      print('longRunningOperation2: $i');
      await null;
    }
  }
}

答案 2 :(得分:0)

我正在使用包装器将慢速操作生成为单独的Isolate并返回a Future。它还允许传递函数运行以及一些参数。

import 'dart:async';
import 'dart:isolate';

/// Example
///
/// ```
/// main() async {
///   String str;
///   str = await runAsync<String, String Function(String)>(sing, ["lalalala"]);
///   print(str);
///
///   str = await runAsync<String, Function>(song);
///   print(str);
/// }
/// 
/// String sing(String str) => "Singing: " + str;
/// String song() => "lololololo";
/// ```

Future<R> runAsync<R, F>(F func, [List<dynamic> parameters]) async {
  final receivePort = ReceivePort();
  await Isolate.spawn(asyncRunner, receivePort.sendPort);

  // The 'asyncRunner' isolate sends it's SendPort as the first message
  final sendPort = await receivePort.first;

  final responsePort = ReceivePort();
  sendPort.send([responsePort.sendPort, func, parameters ?? []]);
  final res = await responsePort.first;
  if (res is! R)
    return Future.error(res);
  else if (res == null) return null;
  return res as R;
}

// Isolate entry point
void asyncRunner(SendPort sendPort) async {
  // Open the ReceivePort for incoming messages
  final port = ReceivePort();

  // Notify our creator the port we listen to
  sendPort.send(port.sendPort);

  final msg = await port.first;

  // Execute
  final SendPort replyTo = msg[0];
  final Function myFunc = msg[1];
  final List<dynamic> parameters = msg[2] ?? [];

  try {
    switch (parameters.length) {
      case 0:
        replyTo.send(myFunc());
        break;
      case 1:
        replyTo.send(myFunc(parameters[0]));
        break;
      case 2:
        replyTo.send(myFunc(parameters[0], parameters[1]));
        break;
      case 3:
        replyTo.send(myFunc(parameters[0], parameters[1], parameters[2]));
        break;
      case 4:
        replyTo.send(
            myFunc(parameters[0], parameters[1], parameters[2], parameters[3]));
        break;
      case 5:
        replyTo.send(myFunc(parameters[0], parameters[1], parameters[2],
            parameters[3], parameters[4]));
        break;
      default:
        replyTo.send(Exception("Unsupported argument length"));
    }
  } catch (err) {
    replyTo.send(Exception(err.toString()));
  }

  // Done
  port.close();
  Isolate.current.kill();
}

https://github.com/vocdoni/dvote-dart/blob/main/lib/util/asyncify.dart#L16

相关问题