4

我与java有关如何执行线程/异步。我使用 new Thread(target).start() 其中 target 是 Runnable 作为在 java 中执行线程的一种方法。新的并发 api 有替代方案,但我们知道在特定的调用中,新线程正在创建并执行传入的任务。

同样,在 Dart 中异步是如何完成的?我阅读了发送/接收端口、完成者/未来、spawnFunction。对我来说,只有 spawnFunction 是令人信服的声明,它将创建新线程。可以解释一下完成者/未来的帮助。我知道他们接受回调,但是 javascript/dart 中是否有一些隐含的逻辑/规则,回调总是在不同的线程中执行。

下面是飞镖片段/伪代码:

void callback() {
  print("callback called");
}

costlyQuery(sql, void f()) {
  executeSql(sql);
  f();
}

costlyQuery("select * from dual", callback);

我希望我的 costlyQuery 签名作为第二个参数是正确的。所以现在我认为f()after不会executeSql(sql)是异步的。如果可以使异步帮助我理解,可能会采用上面的示例添加完成者/未来。

4

1 回答 1

9

tl;dr:没有隐含的规则是回调不会阻塞。

Javascript 事件队列

在 Javascript 中,没有线程(WebWorkers 除外,但这是不同的)。这意味着如果您的代码的任何部分阻塞,整个应用程序都会被阻塞。回调没有什么神奇之处,它们只是函数:

function longLoop(cb) {
    var i = 1000000;
    while (i--) ;
    cb();
}

function callback() {
    console.log("Hello world");
}

function fun() {
    longLoop(callback);
    console.log("Called after Hello World is printed");
}

要进行异步操作,必须将回调放在事件队列中,这仅通过一些 API 调用发生:

  • 用户发起的事件处理程序——点击、键盘、鼠标
  • API 事件处理程序 - XmlHTTPRequest 回调、WebWorker 通信
  • 计时函数 - setTimeout, setInterval

当事件被触发时,回调被放置在事件队列中,待所有其他回调完成执行后执行。意味着不会有两行代码同时执行。

期货

我想你至少偶然发现了这篇关于期货的帖子。如果不是,那么它是一本好书,并且直接来自马口。

在 Javascript 中,您必须做一些工作才能理解异步代码。甚至还有一个名为futures的 Javascript 库,用于处理常见的事情,例如异步循环和序列(完全公开,我亲自与这个库的作者合作过)。

未来的概念是由创建未来的功能做出的承诺,即未来将在未来的某个时候完成。如果您要进行一些异步调用,请创建一个未来,将其返回,并在异步调用完成时履行承诺。来自博客文章:

Future<Results> costlyQuery() {
    var completer = new Completer();

    database.query("SELECT * FROM giant_table", (results) {
        // when complete
        completer.complete(results);
    });

    // this returns essentially immediately,
    // before query is finished
    return completer.future; 
}

如果 database.query 是阻塞调用,future 将立即完成。这个例子假设database.query是一个非阻塞调用(异步),所以future会在这个函数退出后实现。将使用指定的参数completer.complete调用传递给的任何函数。completer.then()

您的示例,修改为惯用和异步:

void callback() {
  print("callback called");
}

costlyQuery(sql) {
  var completer = new Completer();
  executeSql(sql, () => completer.complete());
  return completer.future;
}

costlyQuery("select * from dual").then(callback);

这是异步的并正确使用期货。您可以将自己的回调函数传递给costlyQuery您所做的事情,并在回调中调用它executeSql以实现相同的目的,但这是 Javascript 的做法,而不是 Dart 的方式。

分离物

隔离类似于 Java 的线程,除了隔离是并发模型和线程是并行模型(有关并发与并行的更多信息,请参阅此 SO 问题)。

Dart 的特殊之处在于规范不要求所有内容都在单个线程中运行。它只要求不同的执行上下文不能访问相同的数据。每个隔离区都有自己的内存,并且只能通过发送/接收端口与其他隔离区通信(例如发送数据)。

如果您熟悉的话,这些端口的工作方式类似于 Go 中的通道。

隔离是独立于其他代码运行的隔离执行上下文。在 Javascript 术语中,这意味着它有自己的事件队列。请参阅Dart 导览,了解更完整的隔离物介绍。

假设您的 executeSql 是一个阻塞函数。如果我们不想等待它完成,我们可以将它加载到隔离中:

void callback() {
    print("callback called");
}

void costlyQuery() {
    port.receive((sql, reply) {
        executeSql(sql);
        reply.send();
    });
}

main() {
    var sendPort = spawnFunction(costlyQuery);

    // .call does all the magic needed to communicate both directions
    sendPort.call("select * from dual").then(callback);

    print("Called before executeSql finishes");
}

此代码创建一个隔离,向其发送数据,然后在完成时注册一个回调。即使executeSql阻塞,main()也不一定会阻塞。

于 2012-11-22T09:42:46.520 回答