【问题标题】:async programming in dartDart 中的异步编程
【发布时间】:2012-11-22 07:17:25
【问题描述】:

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

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

下面是 dart sn-p/伪代码:

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

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

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

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

【问题讨论】:

    标签: asynchronous dart


    【解决方案1】:

    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

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

    期货

    我假设您至少偶然发现了this post about futures。如果没有,那是一本好书,而且直接来自马口。

    在 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是一个非阻塞调用(async),所以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 方式。

    分离物

    Isolates 类似于 Java 的线程,只是isolates 是一种并发模型,而线程是一种并行模型(有关并发与并行的更多信息,请参阅this SO question)。

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

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

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

    假设您的 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() 也不一定会阻塞。

    【讨论】:

    • 感谢您的精彩解释。不过我有一个疑问。哪个 api 调用(应该是你列出的 UI、API、setInterval/setTimeout 之一)用于使 database.query 非阻塞。 Seth 有replied here,但在他的实现中,我再次看不到它是如何实现异步的。
    • button.on.click.add((e){costlyQuery((){expensiveWork()})});是不是在执行 costlyQuery 时单击按钮时它会返回而无需等待昂贵的工作()匿名函数执行和完成。这就是javascript对添加到事件队列并让主线程处理事件队列上的其他事件然后最终执行昂贵工作()回调的回调所做的事情吗?
    猜你喜欢
    • 2022-01-07
    • 2020-01-14
    • 2018-12-25
    • 1970-01-01
    • 2021-05-15
    • 2021-10-04
    • 2020-10-21
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多