精华内容
下载资源
问答
  • Dart异步处理机制

    2019-10-20 06:58:13
    不同操作语言处理耗时任务有不同处理机制: 多线程,开启一个新的线程,在新线程中进行异步操作,在通过线程间通信,将数据同主线程共享。Java和C++使用这种方式。 单线程+事件循环,JavaScript和Dart就是甩这种...

    Dart的异步处理机制概览

    不同操作语言处理耗时任务有不同的处理机制:

    • 多线程,开启一个新的线程,在新线程中进行异步操作,在通过线程间通信,将数据同主线程共享。Java和C++使用这种方式。
    • 单线程+事件循环,JavaScript和Dart就是使用这种方式。
      对于多线程而言,耗时操作会阻塞线程执行,而单线程+事件循环的方式,耗时任务是非阻塞的,下面将具体介绍其工作原理。

    Dart与事件循环

    基本概念

    事件循环并不复杂,Dart维护着一个事件队列(Event Queue),并对事件进行存取操作:

    • ,将需要处理的一系列事件(点击事件、I/O事件、网络事件)放在事件队列中。
    • ,不断在事件队列中取出事件,并执行其对应代码块,直到事件队列清空。
      Dart应用程序在Main Isolate执行main函数之后开始运行,当main函数执行完毕退出后,Main Isolate的线程开始一个个的处理应用程序的事件队列中的内容。下图是简化的流程:
      在这里插入图片描述
      Dart维护着一个事件循环以及两个队列:event事件队列和microtask微任务队列。
    • 事件队列包含所有外部事件,如:I/O、鼠标事件、定时器、Isolate之间的消息等等。
    • 微任务通常来源于Dart内部,微任务队列优先级高于事件队列,且数量非常少。
    任务调度
    任务执行顺序

    在Dart单线程模型中,代码执行顺序如下:

    1. Dart的入口是main函数,所以main函数中的代码会优先执行;
    2. main函数执行完后,会启动一个事件循环(Event Loop),并开始执行队列中的任务;
    3. 按照FIFO(先进先出)的顺序,执行 微任务队列(Microtask Queue)中的所有任务;
    4. 当微任务队列中任务为空时,按照FIFO(先进先出)的顺序,执行 事件队列(Event Queue)中的所有任务;
      消息循环

    注意:当事件循环正在处理微任务队列的时候。事件队列会被堵塞。这时候APP就无法进行UI绘制,响应鼠标事件和I/O等事件。

    虽然可以预测任务执行的顺序,但是我们无法预测事件循环什么时候会从队列中提取任务。Dart事件处理系统基于单线程循环,而不是基于时基(tick,系统的相对时间单位)或者其他的时间度量。例如,当你创建一个延时1s的任务,1s后向事件队列添加一个任务,但在该任务之前的任务结束前,事件循环是不会处理这个任务的,也就是说该任务执行可能是大于1s的。
    我们通过Future链指定任务执行顺序:

    future.then(...set an important variable...)
      .then((_) {...use the important variable...});
    

    也可以使用Future.whenComplete()来指定任务在最后被调用。

    事件队列

    可以使用new Future()new Future.delayed()向事件队列种添加事件,这是dart:async中定义的两个Future的构造函数。
    值得注意的是,使用new Future.delayed()在延时一定时间后向队列插入一个任务,这个任务想要执行必须满足下面几点:

    1. main方法执行完毕
    2. 微任务队列为空
    3. 该任务前的任务全部执行完
    微任务队列

    dart:async中定义了scheduleMicrotask()为我们提供创建微任务队列的方法:

    scheduleMicrotask(() {
      // ...code goes here...
    });
    

    创建一个简单的用于打印的微任务:

    import "dart:async";
    
    main(List<String> args) {
      scheduleMicrotask(() {
        print("I am microtask");
      });
    }
    

    关于执行顺序,我做了下面几点总结:

    1. 队列执行优先级:main方法 > 微任务队列 > 事件队列,只有优先级高的队列清空,才会执行低优先级任务
    2. 队列任务执行优先级,按照FIFO(先进先出)原则,先入队先执行
    3. 使用Future.delay()延时任务执行,是在执行该语句延时一定时长后在将Future任务插入队列尾,在测试中一般任务是最后执行
    4. Future如果执行完再添加then,该任务会被放入微任务队列中,当前Future执行完后会立刻执行该微任务,执行完该微任务后才执行下一个Future,另外,该微任务如果新建一个Future并没返回,该Future将插入事件队列(除延时任务)的队尾
    5. Future是链式调用,Futurethen 未执行完,下一个then 不会执行。
      为了方便理解,下面提供了些练习~Let’s go!
    练习测试

    练习一:

    import "dart:async";
    
    main(List<String> args) {
      new Future(() => print('future'));
      scheduleMicrotask(() => print('microtask'));
      print('main');
    }
    

    运行结果:

    main
    microtask
    future
    

    Dart中事件的执行顺序:main方法 > 微任务队列 > 事件队列,对应总结中的1,2点。

    练习二:

    import 'dart:async';
    main() {
      print('main #1 of 2');
      scheduleMicrotask(() => print('microtask #1 of 2'));
    
      new Future.delayed(new Duration(seconds:1),
                         () => print('future #1 (delayed)'));
      new Future(() => print('future #2 of 3'));
      new Future(() => print('future #3 of 3'));
    
      scheduleMicrotask(() => print('microtask #2 of 2'));
    
      print('main #2 of 2');
    }
    

    运行结果:

    main #1 of 2
    main #2 of 2
    microtask #1 of 2
    microtask #2 of 2
    future #2 of 3
    future #3 of 3
    future #1 (delayed)
    

    该练习是练习一的加强版,同时对应总结第3点,Future.delay()将事件放入事件队列队尾。
    练习三:

    import 'dart:async';
    
    main() {
      print('main #1 of 2');
      scheduleMicrotask(() => print('microtask #1 of 3'));
    
      new Future.delayed(new Duration(seconds:1),
          () => print('future #1 (delayed)'));
    
      new Future(() => print('future #2 of 4'))
          .then((_) => print('future #2a'))
          .then((_) {
        print('future #2b');
        scheduleMicrotask(() => print('microtask #0 (from future #2b)'));
      })
          .then((_) => print('future #2c'));
    
      scheduleMicrotask(() => print('microtask #2 of 3'));
    
      new Future(() => print('future #3 of 4'))
          .then((_) => new Future(
          () => print('future #3a (a new future)')))
          .then((_) => print('future #3b'));
    
      new Future(() => print('future #4 of 4'));
      scheduleMicrotask(() => print('microtask #3 of 3'));
      print('main #2 of 2');
    }
    

    运行结果:

    main #1 of 2
    main #2 of 2
    microtask #1 of 3
    microtask #2 of 3
    microtask #3 of 3
    future #2 of 4
    future #2a
    future #2b
    future #2c
    microtask #0 (from future #2b)
    future #3 of 4
    future #4 of 4
    future #3a (a new future)
    future #3b
    future #1 (delayed)
    

    首先,根据规则1~3,main()函数先执行,然后微任务执行,然后有延时的事件任务是最后:

    main #1 of 2
    main #2 of 2
    microtask #1 of 3
    microtask #2 of 3
    microtask #3 of 3
    .
    .
    .
    future #1 (delayed)
    

    接着事件队列开始执行,根据总结5,链式调用:

    future #2 of 4
    future #2a
    future #2b
    future #2c
    

    而在future #2b中新建微任务future #3a (a new future),会在当前Future执行完后执行
    接着开始执行future #3, 在其then函数中,新建future #3a并插入队尾,由于future #3a 没执行,所以future #3b也不会马上执行。
    这时,队尾情况变为

    future #3a (a new future)
    future #3b
    future #1 (delayed)
    之后执行future #4 of 4
    

    需要注意的是,如果把future #3then的代码
    .then((_) => new Future( () => print(‘future #3a (a new future)’)))
    改为.then((_){ new Future( () => print(‘future #3a (a new future)’));})
    因为没有return语句,这时候回调函数返回的是Null Future,future #3a添加到事件队列,但不会阻塞future #3b执行。
    执行的结果如下,大家可以体会下其中差别:

    main #1 of 2
    main #2 of 2
    microtask #1 of 3
    microtask #2 of 3
    microtask #3 of 3
    future #2 of 4
    future #2a
    future #2b
    future #2c
    microtask #0 (from future #2b)
    future #3 of 4
    future #3b
    future #4 of 4
    future #5 of 5
    future #3a (a new future)
    future #1 (delayed)
    

    练习四

    import 'dart:async';
    main() {
      print('main #1 of 2');
      Future future1 = new Future(() => print('future #1 of 5'));
      Future future2 = new Future(() =>  null);
      Future future3 = new Future.delayed(Duration(seconds: 1) ,() => print('future #3 of 5'));
      Future future4 = new Future(() => null);
      Future future5 = new Future(() => null);
    
      future5.then((_) => print('future #5a'));
      future4.then((_) {
        print('future #4a');
        new Future(() => print('future #4b (a new future)'));
        future2.then((_) {
          print('future #2a from future4 #4a');
        });
      });
      future2.then((m) {
        print('future #2b');
      });
      print('main #2 of 2');
    }
    

    运行结果:

    main #1 of 2
    main #2 of 2
    future #1 of 5
    future #2b
    future #4a
    future #2a from future4 #4a
    future #5a
    future #4b (a new future)
    future #3 of 5
    

    练习三基本包括了练习四的要点,这里就不详细解释 (主要因为懒 ),主要注意的一点是,future4先于feture5定义,future4的then也会先于future5执行

    多核CPU的利用

    为了使应用程序保持响应,应该将任务放入Isolate中。Isolate可能运行在一个单独的进程或线程中,这取决于Dart的具体实现。我们已经知道Dart是单线程的,这个线程有自己可以访问的内存空间以及需要运行的事件循环,我们可以将这个空间系统称之为是一个Isolate,比如Flutter中就有一个Root Isolate,负责运行Flutter的代码,比如UI渲染、用户交互等等。

    那么应该使用多少Isolate隔离区?
    对于计算密集型任务,一般隔离区的数量取决于你CPU有多少可用。

    如何创建隔离区?
    创建Isolate是比较简单的,我们通过Isolate.spawn就可以创建了:

    import "dart:isolate";
    
    main(List<String> args) {
      Isolate.spawn(foo, "I am new Isolate");
    }
    
    void foo(info) {
      print("From new isolate:$info");
    }
    

    参考:
    http://www.cndartlang.com/890.html
    https://juejin.im/post/5d36bd3ff265da1b9570995b
    https://segmentfault.com/a/1190000020398241

    展开全文
  • 简单说一下回调函数,它是一种常用的异步处理方法,好比你去图书馆找管理员出借某一本书,但是管理员查询库房,以及登记出借信息是需要花一定的时间,而此时你无事可做,然后你看到了摆放在柜台一边的杂志,然后你就...

    这篇文章算是我用来水的内容,主要也是其他科普性质的文章我还在收集整理资料中,但是给自己定下了一周出一篇的任务,所以想了想这周要写点啥,那就不妨整理一下最近的工作,其中发现比较常用的一个技术,就是回调函数。

    简单说一下回调函数,它是一种常用的异步处理方法,好比你去图书馆找管理员出借某一本书,但是管理员查询库房,以及登记出借信息是需要花一定的时间,而此时你无事可做,然后你看到了摆放在柜台一边的杂志,然后你就兴致勃勃的阅读起来,过了不知道多久的时间,管理员通知你找到了书,并且办理好了出借手续,你于是愉快的拿走了书走出图书馆。

    回调函数就相当于这一过程的处理方法,在IO编程中通常特别常见。而且基本上大多数语言都提供了实现回调函数的方法,以方便程序员自己在需要的时候去使用。

    我直接贴代码吧,如果你有什么不懂的,可以在下面留言,我看见后会回复的。

    C/C++语言的实现

    #include <iostream>
    
    typedef void(*call1)();
    typedef int(*call2)(int);
    
    void foo(call1 fun, call2 fun2) {
        fun();
        int returns = fun2(30);
        std::cout << "returns " << returns << std::endl;
    }
    
    void bar1() {
        std::cout << "callback 1" << std::endl;
    }
    
    int bar2(int arg) {
        std::cout << "callback 2 with arg " << arg << std::endl;
        return 100;
    }
    
    int main() {
        foo(bar1, bar2);
    }
    

    Objective C语言的实现

    首先,由于Objective C可以直接调用C/CPP的代码,所以其中一种回调的实现方式,可以用C/CPP的形式实现。只不过有些细节,稍微有点不太一样罢了。

    //
    //  main.m
    //  test
    //
    //  Created by Orlando Chen on 2021/4/24.
    //
    
    #import <Foundation/Foundation.h>
    #import "callback.h"
    #import <stdio.h>
    
    void bar1() {
        printf("callback 1\n");
    }
    
    int bar2(int arg) {
        printf("callback 2 with arg %d\n", arg);
        return 100;
    }
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            CallbackClass *base = [[CallbackClass alloc] init];
            [base foo:bar1 withAnother:bar2];
        }
        return 0;
    }
    

    Callback.h和Callback.m文件分别如下:

    #ifndef callback_h
    #define callback_h
    
    typedef void(*callback1)(void);
    typedef int(*callback2)(int);
    
    @interface CallbackClass : NSObject
    
    -(void) foo: (callback1)func1 withAnother:(callback2)func2;
    
    @end
    
    
    #endif /* callback_h */
    
    
    
    #import <Foundation/Foundation.h>
    #import "callback.h"
    
    @implementation CallbackClass
    
    -(void) useCallback: (callback1)func1 withAnother:(callback2)func2 {
        func1();
        func2(100);
    };
    
    @end
    

    此外还可以用闭包的形式实现

     .h /
    typedef BOOL(^objcBlock)(int);
    
    @interface CallbackClass : NSObject
    
    -(void) useBlock: (objcBlock)block;
    
    @end
    
    
     .m /
    #import <Foundation/Foundation.h>
    #import "callback.h"
    
    @implementation CallbackClass
    
    -(void) useBlock: (objcBlock)block {
        BOOL val = block(100);
        NSLog(@"result: %@", val?@"YES":@"No");
    };
    
    @end
    

    它的调用形式如下:

    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            CallbackClass *base = [[CallbackClass alloc] init];
            
            [base useBlock:^BOOL(int param) {
                NSLog(@"input number is %d", param);
                return TRUE;
            }];
        }
        return 0;
    }
    

    如果选择不使用Xcode,而是直接用GCC编译代码的话,需要在命令行中使用 -framework 命令,导入最常用的Objective C代码的Foundation框架,然后-o编译指定的二进制可执行文件:

    gcc -framework Foundation -o xxx xxx.m

    Kotlin语言的实现

    Kotlin是谷歌现在主推的一种语言,与Java和JavaScript有很好的亲和性,在后端开发中也经常用到的一种技术,它的回调函数实现方式又是这样的。

    fun foo(callback:(Int)->Unit) {
    	callback(100)
    } 
    
    fun main() {
        foo(){ input ->
            println("input: " + input)
        }
        
        val bar:(numb: Int)->Unit = { arg->
            println("input: " + arg)
        }
        
        foo(bar)
    }
    

    你会发现这里使用了闭包的概念,然后在Swift中,你也能看到类似的概念

    Swift语言的实现

    因为是苹果主推的和Objective C并行的语言,所以在习惯上和Objective C有很多相似的地方,当然设计理念上,和它最相似的还是Kotlin语言。

    typealias CodeClosure1 = () -> Void
    typealias CodeClosure2 = (_ arg1: String, _ arg2: String) -> Void
    
    func foo(arg1: CodeClosure1, arg2: CodeClosure2) {
        arg1()
        arg2("input_1", "intput_2")
    }
    
    func bar1() {
        print("callback1")
    }
    
    func bar2(_ arg1: String, _ arg2: String) {
        print(arg1, arg2)
    }
    
    foo(arg1: bar1, arg2: bar2)
    

    Java语言的实现

    Java实现回调,其实特别繁琐,主要是需要定义一个接口类,然后通过接口进行处理,大概是这么一个思路:

    public abstract class CallbackInterface {
        public abstract void foo();
    }
    
    
    public class Callback {
        public void function(CallbackInterface callback) {
            callback.foo();
        }
    }
    
    
    public class MainClass {
        public static void main(String args[]) {
            Callback callback = new Callback();
            callback.function(new CallbackInterface() {
                @Override
                public void foo() {
                    System.out.println("hello world!");
                }
            });
        }
    }
    

    好像在 Java 8 以后新增了Lambda表达式后,可以用类似闭包的方式实现回调,我估计实现这个功能不是特别复杂,不过我本身已经差不多快10年没碰Java了,所以具体的我就不太清楚。

    C#语言的实现

    C# 可以通过和Java类似的接口类实现回调,也可以通过delegate方法,实现回调的功能。

    using System;
    
    
    namespace CallbackDelegate
    {
        class Program
        {
            public delegate void callback(string url);
    
            public static void foo(callback func)
            {
                func("hello world");
            }
    
            static void Main(string[] args)
            {
                // using anonymous delegate
                foo(delegate(string str) {
                    Console.WriteLine(str);
                });
    
                // using lambda
                foo((str) => Console.WriteLine(str));;
            }
        }
    }
    
    

    Javascript语言的实现

    Javascript 实在很方便,基本上就是两三行代码的事,它的实现过程如下:

    function doSomething(callback){
        if(typeof callback == "function") callback();
     } 
    
    doSomething(function(){
        console.log("Hello World");
     }); 
    

    Python语言的实现

    Python语言是我比较喜欢用的编程语言,它也有自己独特的回调函数实现方式,而且相对来说,它的实现方式非常简单:

    def bar(params):
    	print(params)
    
    def foo(fun):
    	fun(100)
    
    foo(bar)
    

    如果这篇文章帮到了你,点个赞再走~祝好运!

    展开全文
  • 1.js中的同步加载和异步加载有什么不同? javascript语言是单线程机制。所谓单线程就是按次序执行,执行完一个任务再执行下一个。 对于浏览器来说,也就是无法在渲染页面的同时执行代码。 同步加载:也就是说一个...

    1.js中的同步加载和异步加载有什么不同?
    javascript语言是单线程机制。所谓单线程就是按次序执行,执行完一个任务再执行下一个。
    对于浏览器来说,也就是无法在渲染页面的同时执行代码。
    同步加载:也就是说一个事件处理程序没加载完之前,不会加载后面的程序。
    异步加载:也就是并发加载,会同时处理几个事件处理程序。
    举个生活中的例子:
    早上起床,先刷牙,再烧水,等水烧开了洗脸,再整理发型.是同步
    先刷牙,再烧水,再整理发型,等水壶滴的一声通知我水烧开了,我再取刚烧开的水洗脸,是异步.
    2.js是同步加载的

    <!DOCTYPE html>
    <html lang="en">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <meta http-equiv="X-UA-Compatible" content="ie=edge">
        <title>Document</title>
    </head>
    <body>
        <div>
            <strong></strong>
        </div>
        <p></p>
        <span></span>
    </body>
    </html>
    
    

    js在执行上述代码时:
    首先绘制DomTree:
    在这里插入图片描述
    3.JS实现异步加载的三种方式
    1.defer 异步加载,但是要等到dom文档全部解析完毕才会被执行。(可以在行间书写js代码)(只适用于IE)

    <script type="text/javascript" src="demo.js" defer="defer"></script>
    
    //行内js
    <script type="text/javascript"  defer="defer">
    console.log(“a”);
    </script>
    

    2.async 异步加载1,加载完毕就会执行,async只能加载外部js脚本,不能把js书写在script标签内(W3C标准方法)

    <script type="text/javascript" src="demo.js" async="async"></script>
    

    3.创建script,插入到DOM中,加载完毕后callback(通吃的方法)

    <body>
        <script>
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = '123.js';
        document.head.appendChild(script);   
        
        //123.js 文件
    //     function test(){
    //     console.log('a');
    // }
        </script>
    </body>
    

    直接调用 外部链接js文件中的test()函数;

      <script>
        var script = document.createElement('script');
        script.type = 'text/javascript';
        script.src = '123.js';
        document.head.appendChild(script);   
        
        //123.js 文件
    //     function test(){
    //     console.log('a');
    // }
        //直接调用 test();
       // test();  
        // test is not undefined  (因为js文件会有一个加载的过程)
    
        //使用定时器,1s后调用test();
        setTimeout(function(){
            test();
        },1000)  //1s后打印了a
        </script>
    

    判断js文件是否加载完毕之后进行后续操作:
    chrome,oprea,firefox,safari

     script.onload = function(){
                  test();
                 }  //script.onload 在js加载完后触发```
    
    
    

    ie

     script.onreadystatechange = function(){
                    if(script.readyState == 'complete' || script.readyState == 'loaded')
                     {
                         test();
                     }
                 }  //根据状态变化判断js是否加载完毕执行后续操作
    
    

    封装函数:

    
        <script>
        function loadscript(url,callback){
        var  script = document.createElement('script');
        script.type = "text/javascript";
        //chrome,opera,firefox,safari
        script.onload = function(){
            callback();
        }
        //IE
        script.onreadystatechange = function(){
            if(this.readyState == 'complete' || 
            this.readyState == 'loaded')
            {
                callback();
            }
        }
        script.src = url;
        document.head.appendChild(script);
    }
    loadscript('123.js',function (){
        test();
    });
    </script>
    

    因为IE会有状态判断的过程,如果程序小所消耗的时间极少,那么ie状态下函数就不会被触发,所以将加载js文件放到最后可以避免这个错误。

    展开全文
  • Dart的异步

    2021-04-19 15:27:56
    针对如何处理耗时的操作,不同语言不同处理方式。 多线程,不如Java、C++,我们普遍的做法是开启一个新的线程,在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。 单

    一、Dart的异步模型
    1.1 Dart是单线程的

    1.1.1 程序中的耗时操作
    开发中的耗时操作:

    • 在开发中,我们经常会遇到一些耗时的操作需要完成,比如网络请求、文件读取等等;
    • 如果我们的主线程一直在等待这些耗时的操作完成,那么就会进程阻塞,无法响应其他事件,比如用户点击事件等

    如何处理耗时操作?
    针对如何处理耗时的操作,不同的语言有不同的处理方式。

    • 多线程,不如Java、C++,我们普遍的做法是开启一个新的线程,在新的线程中完成这些异步的操作,再通过线程间通信的方式,将拿到的数据传递给主线程。
    • 单线程+事件循环,比如JavaScript、Dart都是基于单线程加事件循环来完成耗时操作的处理,不过单线程如何能进行耗时的操作呢?

    1.1.2单线程的异步操作
    一个应用程序大部分时间都是出于空闲的状态,并不是无限制的在和用户进行交互。比如用户点击、网络请求数据的返回、文件读写操作,这些等待的行为并不会阻塞我们的线程;这是因为类似于网络请求、文件读写,我们都可以基于非阻塞调用。
    阻塞式调用和非阻塞式调用
    阻塞和非阻塞关注的是程序在等待调用结果时的状态。
    阻塞式调用: 调用分会之前,当前线程会被挂起,调用线程只有在得到调用结果之后才会继续执行。
    非阻塞式调用: 调用执行之后,当前线程不会停止,只需过一段时间来检查一下有没有结果返回即可。
    在实际开发中的很多耗时操作,都可以基于非阻塞式调用

    1.2Dart事件循环
    1.2.1什么是事件循环
    单线程模型中主要是维护着一个事件循环。
    事实上,事件循环并不复杂,它就是将需要处理的一系列事件(包括点击事件、读写操作、网络事件)放在一个事件队列中。不断地从事件队列中取出事件,并执行其对应需要执行的代码块,直到事件队列清空位置。
    我们来写一个事件循环的伪代码:

    // 这里我使用数组模拟队列, 先进先出的原则
    List eventQueue = []; 
    var event;
    
    // 事件循环从启动的一刻,永远在执行
    while (true) {
      if (eventQueue.length > 0) {
        // 取出一个事件
        event = eventQueue.removeAt(0);
        // 执行该事件
        event();
      }
    }
    

    当我们有一些事件时,比如点击、读写等操作时,它们就会被加入到队列中,当发现事件队列不为空时,就会取出事件,并且执行。

    二、Dart的异步操作
    Dart中的异步操作主要使用Future以及async、await。

    2.1认识Future
    2.1.1同步的网络请求
    我们先来看一个例子吧:

    在这个例子中,我使用getNetworkData来模拟了一个网络请求;

    该网络请求需要3秒钟的时间,之后返回数据;

    import "dart:io";
    
    main(List<String> args) {
      print("main function start");
      print(getNetworkData());
      print("main function end");
    }
    
    String getNetworkData() {
      sleep(Duration(seconds: 3));
      return "network data";
    }
    

    这段代码会运行怎么的结果呢?

    getNetworkData会阻塞main函数的执行

    main function start
    // 等待3秒
    network data
    main function end
    

    显然,上面的代码不是我们想要的执行效果,因为网络请求阻塞了main函数,那么意味着其后所有的代码都无法正常的继续执行。

    2.1.2异步的网络操作
    我们可以对上面的代码进行改进,使用Future对象来将耗时的操作放在了其中传入的函数中

    import "dart:io";
    
    main(List<String> args) {
      print("main function start");
      print(getNetworkData());
      print("main function end");
    }
    
    Future<String> getNetworkData() {
      return Future<String>(() {
        sleep(Duration(seconds: 3));
        return "network data";
      });
    }
    

    我们来看一下代码的运行结果:

    1、这一次的代码顺序执行,没有出现任何的阻塞现象;
    2、和之前直接打印结果不同,这次我们打印了一个Future实例;
    结论:我们将一个耗时的操作隔离了起来,这个操作不会再影响我们的主线程执行了。
    问题:我们如何去拿到最终的结果呢?

    main function start
    Instance of 'Future<String>'
    main function end
    

    获取Future得到的结果

    有了Future之后,如何去获取请求到的结果:通过.then的回调:

    main(List<String> args) {
      print("main function start");
      // 使用变量接收getNetworkData返回的future
      var future = getNetworkData();
      // 当future实例有返回结果时,会自动回调then中传入的函数
      // 该函数会被放入到事件循环中,被执行
      future.then((value) {
        print(value);
      });
      print(future);
      print("main function end");
    }
    

    上面代码的执行结果:

    main function start
    Instance of 'Future<String>'
    main function end
    // 3s后执行下面的代码
    network data
    

    执行中出现异常
    如果调用过程中出现了异常,拿不到结果,如何获取到异常的信息呢?catchError可以捕获到异常信息

    
    import "dart:io";
    
    main(List<String> args) {
      print("main function start");
      var future = getNetworkData();
      future.then((value) {
        print(value);
      }).catchError((error) { // 捕获出现异常时的情况
        print(error);
      });
      print(future);
      print("main function end");
    }
    
    Future<String> getNetworkData() {
      return Future<String>(() {
        sleep(Duration(seconds: 3));
        // 不再返回结果,而是出现异常
        // return "network data";
        throw Exception("网络请求出现错误");
      });
    }
    

    上面代码的执行结果:

    main function start
    Instance of 'Future<String>'
    main function end
    // 3s后没有拿到结果,但是我们捕获到了异常
    Exception: 网络请求出现错误
    

    2.1.3 Future使用补充

    补充一:案例总结
    我们通过一个案例来学习了一些Future的使用过程:
    1、创建一个Future(可能是我们创建的,也可能是调用内部API或者第三方API获取到的一个Future,总之你需要获取到一个Future实例,Future通常会对一些异步的操作进行封装);
    2、通过.then(成功回调函数)的方式来监听Future内部执行完成时获取到的结果;
    3、通过.catchError(失败或异常回调函数)的方式来监听Future内部执行失败或者出现异常时的错误信息;

    补充二:Future的两种状态
    事实上Future在执行的整个过程中,我们通常把它划分成了两种状态:

    状态一:未完成状态(uncompleted)
    执行Future内部的操作时(在上面的案例中就是具体的网络请求过程,我们使用了延迟来模拟),我们称这个过程为未完成状态

    状态二:完成状态(completed)
    当Future内部的操作执行完成,通常会返回一个值,或者抛出一个异常。这两种情况,我们都称Future为完成状态。

    补充三:Futura的链式调用
    上面代码我们可以进行如下的改进:

    我们可以在then中继续返回值,会在下一个链式的then调用回调函数中拿到返回的结果

    import "dart:io";
    
    main(List<String> args) {
      print("main function start");
    
      getNetworkData().then((value1) {
        print(value1);
        return "content data2";
      }).then((value2) {
        print(value2);
        return "message data3";
      }).then((value3) {
        print(value3);
      });
    
      print("main function end");
    }
    
    Future<String> getNetworkData() {
      return Future<String>(() {
        sleep(Duration(seconds: 3));
        // 不再返回结果,而是出现异常
         return "network data1";
      });
    }
    

    打印结果如下:

    main function start
    main function end
    // 3s后拿到结果
    network data1
    content data2
    message data3
    

    补充四:Futura的其他API
    Future.value(value)
    直接获取一个完成的Futura,改Futura会直接调用then的回调函数。

    main(List<String> args) {
      print("main function start");
    
      Future.value("哈哈哈").then((value) {
        print(value);
      });
      print("main function end");
    }
    

    打印结果如下:

    main function start
    main function end
    哈哈哈
    

    疑惑:为什么立即执行,但是哈哈哈是在最后打印的呢?
    这是因为Future中的then会作为新的任务会加入到事件队列中(Event Queue),加入之后你肯定需要排队执行了。

    Future.error(object)
    直接获取一个完成的Future,但是是一个发生异常的Future,该Future会直接调用catchError的回调函数。

    main(List<String> args) {
      print("main function start");
    
      Future.error(Exception("错误信息")).catchError((error) {
        print(error);
      });
    
      print("main function end");
    }
    

    打印结果如下:

    main function start
    main function end
    Exception: 错误信息
    

    Future.delayed(时间, 回调函数)
    在延迟一定时间时执行回调函数,执行完回调函数后会执行then的回调;

    main(List<String> args) {
      print("main function start");
    
      Future.delayed(Duration(seconds: 3), () {
        return "3秒后的信息";
      }).then((value) {
        print(value);
      });
      print("main function end");
    }
    

    2.2 await 、async

    await、async是什么呢?
    它们是Dart中的关键字。
    它们可以让我们用同步的代码格式,去实现异步的调用过程。并且,通常一个async的函数会返回一个Future。
    我们已经知道,Future可以做到不阻塞我们的线程,让线程继续执行,并且在完成某个操作时改变自己的状态,并且回调then或者errorCatch回调。

    在Future.delayed函数前加了一个await。

    String getNetworkData() {
      var result = await Future.delayed(Duration(seconds: 3), () {
        return "network data";
      });
    
      return  "请求到的数据:" + result;
    }
    

    一旦有了await这个关键字,那么这个操作就会等待Future.delayed的执行完毕,并且等待它的结果。
    执行代码会看到以下的错误
    在这里插入片描述
    错误非常明显:await关键字必须存在于async函数中。
    所以我们需要将getNetworkData函数定义成async函数。也非常简单,只需要在函数的()后面加上一个async关键字就可以了。

    String getNetworkData() async {
      var result = await Future.delayed(Duration(seconds: 3), () {
        return "network data";
      });
      return  "请求到的数据:" + result;
    }
    

    执行后依然报错:
    在这里插入图片描述
    错误非常明显:使用async标记的函数,必须返回一个Future对象。所以我们需要继续修改代码,将返回值写成一个Future。

    import "dart:io";
    
    main(List<String> args) {
      print("main function start");
      var future = getNetworkData();
      future.then((val){
        print(val);
      });
      print("main function end");
    }
    
    Future<String> getNetworkData() async {
      var result = await Future.delayed(Duration(seconds: 3), () {
        return "network data";
      });
    
      return "请求到的数据:$result";
    }
    

    执行结果
    在这里插入图片描述
    这段代码应该是我们理想当中执行的代码了
    我们现在可以像同步代码一样去使用Future异步返回的结果;
    等待拿到结果之后和其他数据进行拼接,然后一起返回;
    返回的时候并不需要包装一个Future,直接返回即可,但是返回值会默认被包装在一个Future中;

    2.3. 读取json案例
    读取json案例代码

    import 'package:flutter/services.dart' show rootBundle;
    import 'dart:convert';
    import 'dart:async';
    
    main(List<String> args) {
      getAnchors().then((anchors) {
        print(anchors);
      });
    }
    
    class Anchor {
      String nickname;
      String roomName;
      String imageUrl;
    
      Anchor({
        this.nickname,
        this.roomName,
        this.imageUrl
      });
    
      Anchor.withMap(Map<String, dynamic> parsedMap) {
        this.nickname = parsedMap["nickname"];
        this.roomName = parsedMap["roomName"];
        this.imageUrl = parsedMap["roomSrc"];
      }
    }
    Future<List<Anchor>> getAnchors() async {
      // 1.读取json文件
      String jsonString = await rootBundle.loadString("assets/aa.json");
    
      // 2.转成List或Map类型
      final jsonResult = json.decode(jsonString);
    
      // 3.遍历List,并且转成Anchor对象放到另一个List中
      List<Anchor> anchors = new List();
      for (Map<String, dynamic> map in jsonResult) {
        anchors.add(Anchor.withMap(map));
      }
      return anchors;
    }
    

    三. Dart的异步补充

    3.1. 任务执行顺序
    3.1.1. 认识微任务队列
    在前面学习学习中,我们知道Dart中有一个事件循环(Event Loop)来执行我们的代码,里面存在一个事件队列(Event Queue),事件循环不断从事件队列中取出事件执行。
    但是如果我们严格来划分的话,在Dart中还存在另一个队列:微任务队列(Microtask Queue)。
    微任务队列的优先级要高于事件队列;也就是说事件循环都是优先执行微任务队列中的任务,再执行 事件队列 中的任务;

    那么在Flutter开发中,哪些是放在事件队列,哪些是放在微任务队列呢?
    所有的外部事件任务都在事件队列中,如IO、计时器、点击、以及绘制事件等;而微任务通常来源于Dart内部,并且微任务非常少。这是因为如果微任务非常多,就会造成事件队列排不上队,会阻塞任务队列的执行(比如用户点击没有反应的情况).
    那么在Dart的单线程中,代码到底是怎样执行的呢?

    1. Dart的入口是main函数,所以main函数中的代码会优先执行;
    2. main函数执行后,会启动一个事件循环,开始执行队列中的任务;
    3. 首先按照先进先出的顺序,执行微任务队列中的所有任务;
    4. 其次,会按照先进先出的顺序,执行事件队列中的所有任务。

    3.1.2. 如何创建微任务
    在开发中,我们可以通过dart中async下的scheduleMicrotask来创建一个微任务:

    import "dart:async";
    
    main(List<String> args) {
      scheduleMicrotask(() {
        print("Hello Microtask");
      });
    }
    

    在开发中,如果我们有一个任务不希望它放在Event Queue中依次排队,那么就可以创建一个微任务了。

    Future的代码是加入到事件队列还是微任务队列呢?
    Future中通常有两个函数执行体:
    Future构造函数传入的函数体;then的函数体(catchError等同看待)
    那么它们是加入到什么队列中的呢?
    Future构造函数传入的函数体放在事件队列中
    then的函数体要分成三种情况:
    情况一:Future没有执行完成(有任务需要执行),那么then会直接被添加到Future的函数执行体后;
    情况二:如果Future执行完后就then,该then的函数体被放到如微任务队列,当前Future执行完后执行微任务队列;
    情况三:如果Future是链式调用,意味着then未执行完,下一个then不会执行;

    // future_1加入到eventqueue中,紧随其后then_1被加入到eventqueue中
    Future(() => print("future_1")).then((_) => print("then_1"));
    
    // Future没有函数执行体,then_2被加入到microtaskqueue中
    Future(() => null).then((_) => print("then_2"));
    
    // future_3、then_3_a、then_3_b依次加入到eventqueue中
    Future(() => print("future_3")).then((_) => print("then_3_a")).then((_) => print("then_3_b"));
    

    3.1.3. 代码执行顺序
    先看代码:

    
    import "dart:async";
    
    main(List<String> args) {
      print("main start");
    
      Future(() => print("task1"));
    	
      final future = Future(() => null);
    
      Future(() => print("task2")).then((_) {
        print("task3");
        scheduleMicrotask(() => print('task4'));
      }).then((_) => print("task5"));
    
      future.then((_) => print("task6"));
      scheduleMicrotask(() => print('task7'));
    
      Future(() => print('task8'))
        .then((_) => Future(() => print('task9')))
        .then((_) => print('task10'));
    
      print("main end");
    }
    

    执行结果:

    main start
    main end
    task7
    task1
    task6
    task2
    task3
    task5
    task4
    task8
    task9
    task10
    

    分析:
    1、main函数先执行,所以main start和main end先执行,没有任何问题;

    2、main函数执行过程中,会将一些任务分别加入到EventQueue和MicrotaskQueue中;

    3、task7通过scheduleMicrotask函数调用,所以它被最早加入到MicrotaskQueue,会被先执行;

    4、然后开始执行EventQueue,task1被添加到EventQueue中被执行;

    5、通过final future = Future(() => null);创建的future的then被添加到微任务中,微任务直接被优先执行,所以会执行task6;

    6、一次在EventQueue中添加task2、task3、task5被执行;

    7、task3的打印执行完后,调用scheduleMicrotask,那么在执行完这次的EventQueue后会执行,所以在task5后执行task4(注意:scheduleMicrotask的调用是作为task3的一部分代码,所以task4是要在task5之后执行的)

    8、task8、task9、task10一次添加到EventQueue被执行;

    3.2. 多核CPU的利用

    3.2.1. Isolate的理解
    在Dart中,有一个Isolate的概念,它是什么呢?

    我们已经知道Dart是单线程的,这个线程有自己可以访问的内存空间以及需要运行的事件循环;

    我们可以将这个空间系统称之为是一个Isolate;

    比如Flutter中就有一个Root Isolate,负责运行Flutter的代码,比如UI渲染、用户交互等等;

    在 Isolate 中,资源隔离做得非常好,每个 Isolate 都有自己的 Event Loop 与 Queue,

    Isolate 之间不共享任何资源,只能依靠消息机制通信,因此也就没有资源抢占问题。

    但是,如果只有一个Isolate,那么意味着我们只能永远利用一个线程,这对于多核CPU来说,是一种资源的浪费。

    如果在开发中,我们有非常多耗时的计算,完全可以自己创建Isolate,在独立的Isolate中完成想要的计算操作。

    如何创建Isolate呢?

    创建Isolate是比较简单的,我们通过Isolate.spawn就可以创建了:

    import "dart:isolate";
    
    main(List<String> args) {
      Isolate.spawn(foo, "Hello Isolate");
    }
    
    void foo(info) {
      print("新的isolate:$info");
    }
    

    3.2.2. Isolate通信机制
    在真实开发中,我们不会只是简单的开启一个新的Isolate,而不关心它的运行结果:

    我们需要新的Isolate进行计算,并且将计算结果告知Main Isolate(也就是默认开启的Isolate);

    Isolate 通过发送管道(SendPort)实现消息通信机制;

    我们可以在启动并发Isolate时将Main Isolate的发送管道作为参数传递给它;

    并发在执行完毕时,可以利用这个管道给Main Isolate发送消息;

    import "dart:isolate";
    
    main(List<String> args) async {
      // 1.创建管道
      ReceivePort receivePort= ReceivePort();
    
      // 2.创建新的Isolate
      Isolate isolate = await Isolate.spawn<SendPort>(foo, receivePort.sendPort);
    
      // 3.监听管道消息
      receivePort.listen((data) {
        print('Data:$data');
        // 不再使用时,我们会关闭管道
        receivePort.close();
        // 需要将isolate杀死
        isolate?.kill(priority: Isolate.immediate);
      });
    }
    
    void foo(SendPort sendPort) {
      sendPort.send("Hello World");
    }
    

    但是我们上面的通信变成了单向通信,如果需要双向通信呢?

    事实上双向通信的代码会比较麻烦;

    Flutter提供了支持并发计算的compute函数,它内部封装了Isolate的创建和双向通信;

    利用它我们可以充分利用多核心CPU,并且使用起来也非常简单;

    注意:下面的代码不是dart的API,而是Flutter的API,所以只有在Flutter项目中才能运行

    main(List<String> args) async {
      int result = await compute(powerNum, 5);
      print(result);
    }
    
    int powerNum(int num) {
      return num * num;
    }
    
    展开全文
  • Go 语言中的超时处理

    2020-06-10 13:44:19
    最近在项目里用 Go 语言对一些微服务进行重构,由于 Go 对我来说仍然算是一种新语言,因此在项目过程中碰到的一些比较不同一些写法,将会以 Tips 的方式记录在这里。今天想记录的是 Go 语言里的超时处理。 超时在...
  • Javascript中的异步编程

    2020-08-12 17:10:21
    Javascript是单线程的,为了能及时响应用户操作,javascript对耗时操作(如Ajax请求、本地文件读取等)的处理异步进行的,也即是所谓的异步编程。除了快速响应用户操作之外,另外一个让javascript采用异步方式的...
  • 针对如何处理耗时的操作,不同语言不同处理方式处理方式一: 多线程,比如Java、OC、Swift、C++,我们普遍的做法是开启一个新的线程(Thread),在新的线程中完成这些异步的操作,再通过线程间通信的
  • 由于Python中GIL全局锁的限制,单是使用多线程threading,无法充分利用CPU,这里需要一个工具实现异步方式来进行分配管理任务。 Celery简介 celery是一个分布式的任务队列,把大量任务分布到不同的机器上去,通过...
  • 多线程编程(python语言

    千次阅读 2018-08-10 16:47:56
    众所周知,多线程编程是一种可以提高整个任务性能的并行处理方式。多线程编程的主要特点有以下几个方面,本质上是异步的;需要多个并发活动;每个活动的处理顺序可能是不确定的,或者说是随机的、不可预测的。这种...
  • 同时框架支持同步、异步两种数据交换方式(不可共存)方便开发者的使用。MLESocket具有的特点:1.开发简便、快速,开发无需底部基础;2.开发不需要处理任何有关数据格式的操作,仅需要完成自身数据处理,返回;3....
  • 消息队列主要有能解决三个问题:异步解耦在分布式系统中,不同应用之间的相互调用,如果采用同步的方式,请求发起方发起调用之后,接收调用方需要在处理完成之后,再同步地返回执行结果给到调用方,在此过程请求发起...
  • 第九章“MIDP 2.0 Push 技术”介绍了如何通过异步方式将信息传送给设备并自动启动 J2ME 中文教程 by www.j2medev.com MIDlet 程序的机制。 第十章“MIDlet 的开发流程与部署”介绍了如何真正完成你的程序并打包发往...
  • 《Java编程和企业应用热点》和《动态语言编程热点》等栏目都是调用同一个数据库知识分类不同而已,难道没有更简单的办法吗? [b]问题补充:[/b] [quote]你可以把这些操作都封装成一个服务层的方法,每次只要传栏目的...
  • 我们希望为应用程序特定的错误提供类型化的接口,该接口的处理方式与关键语言错误(例如SyntaxError等)的处理方式有所不同。此外,我们还编写了异步代码以取消Promise。 这在React useEffect上很明显,您可以在...
  • rabbitmq基础操作(一)

    2018-12-10 20:40:20
    异步处理 消息通讯 RabbitMq的工作原理几种工作模式 RabbitMq的工作原理 RabbitMq将自己定义为一个交换机,这也是在当初选择开发语言的时候选择一门交换机开发语言的原因,当然也正是因为选择了这门语言才让RabbitMq...
  • 游戏服务端技术路线

    千次阅读 2017-11-22 18:04:32
    第一,选择什么样的架构。 不同的游戏适用不同的架构。卡牌游戏架构、MMO游戏架构、MOBA游戏架构、FPS游戏架构 第二,选择单线程还是多线程。...操作系统的同步与异步,进程与线程。...两种处理方式: 一种是跟游
  • AFNetworking Github OC语言排第一, 凝聚了众多 封装了NSURLSession的网络请求, 线程NSOperation最大并发数是1, 保证每个网络请求串行进行 Session中的taskData都是异步执行 ...区分不同的参数拼接方式 对参.
  • Yii2 源码学习 Event

    2019-02-16 18:23:32
    虽然PHP语言本身不能实现异步事件,但是并不代表事件在PHP中不是 一个非常有效的代码注入方式。TinkSNS中就有钩子,允许用户在不修 改主体业务代码的时候执行不同的操作。钩子对于一些活动,比如充 值送积分,充值打...
  • return_url和notify_url

    2014-06-21 11:09:18
    在支付宝工作人员提供的demo中会存在两个返回处理文件(不同语言叫的名称会不一样【详见附录1】),同样对应支付宝API接口参数:同步返回(return_url)和异步返回(notify_url),那么他们的的区别有是在哪呢?...
  • 什么是消息队列(Message queue)

    万次阅读 2018-04-13 21:47:05
    消息队列(英语:Message queue)是一种进程间通信或同一进程的不同线程间的通信方式,软件的贮列用来处理一系列的输入,通常是来自用户。 消息队列提供了异步的通信协议,每一个贮列中的纪录包...
  • 高效使用JavaScript闭包

    2021-02-01 16:21:56
    在Node.js中,广泛采用不同形式的闭包来支持Node的异步和事件驱动编程模型。通过很好地理解闭包,您可以确保所开发应用程序的功能正确性、稳定性和可伸缩性。闭包是一种将数据与处理数据的代码相关联的自然方式,它...
  • HYUploadSRV20190619.rar

    2019-06-21 16:53:50
    1.服务器端采用异步I/O架构设计,具有高性能I/O处理能力,尤其适用于超大文件上传; 2.服务器端采用高效内存分配技术确保在运行过程中服务器的内存开销最小化; 3.完全采用标准协议实现,因此兼容几乎所有的PC端和...
  • C#微软培训教材(高清PDF)

    千次下载 热门讨论 2009-07-30 08:51:17
    17.1 .Net 框架结构提供的 I/O 方式 .215 17.2 文件存储管理 .217 17.3 读 写 文 件 .222 17.4 异步文件操作 .227 17.5 小 结 .234 第十八章 高 级 话 题 .235 18.1 注册表编程 .235 18.2 在 C #代码...
  • JSTL详细标签库介绍

    2010-11-01 16:37:44
    异常处理位置与异常发生位置必须不同(若一个程序能够处理自己的错误,那么就采用传统的错误处理方式进行局部处理)<BR>2、 要避免使用异常处理来代替错误处理,若这样就会降低程序的清晰性。<BR>3、 尽管可以使用...
  • 这说明,语言可以用以前没有的方式交互操作。 ● 对动态Web页面的更好支持:虽然经典ASP具有很大的灵活性,但效率不是很高,这是因为它使用了解释性的脚本语言,且缺乏面向对象的设计,从而导致ASP代码比较混乱。...
  • awesome-python 是 vinta 发起维护的 Python 资源列表,内容包括:Web 框架、网络爬虫、网络内容提取、模板引擎、数据库、数据可视化、图片处理、文本处理、自然语言处理、机器学习、日志、代码分析等。由「开源前哨...

空空如也

空空如也

1 2 3 4 5 ... 13
收藏数 258
精华内容 103
关键字:

不同语言异步处理方式