zoukankan      html  css  js  c++  java
  • .NET(C#):await返回Task的async方法

    众所周知,async方法只可以返回void,Task和Task<T>。

    对于返回void的async方法,它并不是awaitable,所以其他方法不能用await方法来调用它,而返回Task的async方法则可以。

    那么当async方法返回Task后,接着await,那被await的Task是一个什么概念?是async方法中第一个被await的Task?不,它代表目标async方法的全部执行,其中包括被await分割的连接Task,但是不包括非await造成的多线程执行。

    如下代码,在doo是一个返回Task的async方法,然后在另一个方法test中await调用doo,然后在Main方法中调用test(由于Main方法不允许加async,所以需要另外加一个async方法来使用await)

    static void Main(string[] args)

    {

        test();

        log("Main:调用test后");

        Thread.Sleep(Timeout.Infinite);

    }

    //Main方法不允许加async,所以我们用这个方法使用await

    static async void test()

    {

        log("test: await之前");

        await doo();

        log("test: await之后");

    }

    //返回Task的async方法

    static async Task doo()

    {

        log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 1; }));

        log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 2; }));

        log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 3; }));

        Thread.Sleep(1000);

        Console.WriteLine("doo中在Task外的Thread.Sleep执行完毕");

    }

    //输出方法:显示当前线程的ManagedThreadId

    static void log(string msg)

    {

        Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, msg);

    }

    上面代码会输出:

    1: test: await之前

    1: Main:调用test后

    3: Task

    3: doo: Task结果:1

    4: Task

    4: doo: Task结果:2

    3: Task

    3: doo: Task结果:3

    doo中在Task外的Thread.Sleep执行完毕

    3: test: await之后

    前两句简单,调用test方法,await后的内容会被加在目标Task的后面,然后test马上返回,于是输出“Main:调用test后”,同时他们都是在主线程中执行的,所以ManagedThreadId都是1。

    接着后面就是另一个Task的执行(当然在另一个线程,也是test方法中await的目标Task)。这个所谓的Task就是doo方法的全部执行。所以doo中三个顺序执行的Task(通过await一个一个连接)依次执行,所以Task输出结果1,2,3。第一个Task的ManagedThreadId是3,第二个是4,第三个又是3,原因是Task的内部执行使用了CLR的线程池,所以线程得到了重复利用。

    接着doo方法还没有完,最后一个await造成doo方法后面的代码在这个await针对的Task执行后继续执行,于是输出:doo中Task外的Thread.Sleep执行完毕。

    最后当doo彻底执行完test的await才结束,所以最后一行输出:test:await之后。

    上面我说过:被await的async方法返回的Task代表“目标async方法的全部执行,其中包括被await分割的连接Task,但是不包括非await造成的多线程执行”。

    所以如果把返回Task的async方法(也就是上例中的doo方法)改成这样:

    //返回Task的async方法

    static async Task doo()

    {

        log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 1; }));

        log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 2; }));

        log("doo: Task结果:" + await Task.Run(() => { Thread.Sleep(1000); log("Task"); return 3; }));

        //不使用await:线程池多线程

        ThreadPool.QueueUserWorkItem(_ =>

            {

                Thread.Sleep(1000);

                Console.WriteLine("ThreadPool.QueueUserWorkItem");

            });

        //不使用await:Task多线程

        Task.Run(() =>

            {

                Thread.Sleep(1000);

                Console.WriteLine("Task.Run");

            });

    }

    我们加入了不用await的多线程执行,分别使用ThreadPool和Task,整个程序会输出这样的结果:

    1: test: await之前

    1: Main:调用test后

    3: Task

    3: doo: Task结果:1

    4: Task

    4: doo: Task结果:2

    3: Task

    3: doo: Task结果:3

    3: test: await之后

    Task.Run

    ThreadPool.QueueUserWorkItem

    不使用await的多线程完全脱离了test方法中await的Task,是运行在test的await之后的。

    另外Visual Studio会对Task.Run代码做如下警告:
      image


     

    提示:Because this call is not awaited, execution of the current method continues before the call is completed. Consider applying the ‘await’ operator to the result of the call.

    就是说,如果不加await,当前方法会继续执行直到结束,不用管他,因为我们现在就是在做在async方法中不用await的测试,呵呵。www.2cto.com

    或许你会问,为什么要用这样的方式去await另一个async方法返回的Task呢?我们一直在讨论返回Task的async方法,我认为看一个返回Task<T>的async方法可以更好地解释这个问题。

    下面我们把上面的代码改成相似的返回Task<int>的async方法执行,那么doo方法返回Task<T>,他把自己方法内3个awaited Task的结果统一相加,最后返回结果并作为自己返回的Task的结果。然后在test方法中输出doo返回的结果。

    完整代码:

    static void Main(string[] args)

    {

        test();

        log("Main:调用test后");

        Thread.Sleep(Timeout.Infinite);

    }

    //Main方法不允许加async,所以我们用这个方法使用await

    static async void test()

    {

        log("test: await之前");

        Console.WriteLine("doo结果:{0}", await doo());

        log("test: await之后");

    }

    //返回Task的async方法

    static async Task<int> doo()

    {

        var res1 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task1执行"); return

        var res2 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task2执行"); return

        var res3 = await Task.Run(() => { Thread.Sleep(1000); log("awaited Task3执行"); return

        //不使用await:线程池多线程

        ThreadPool.QueueUserWorkItem(_ =>

            {

                Thread.Sleep(1000);

                Console.WriteLine("ThreadPool.QueueUserWorkItem");

            });

        //不使用await:Task多线程

        Task.Run(() =>

            {

                Thread.Sleep(1000);

                Console.WriteLine("Task.Run");

            });

        return res1 + res2 + res3;

    }

    //输出方法:显示当前线程的ManagedThreadId

    static void log(string msg)

    {

        Console.WriteLine("{0}: {1}", Thread.CurrentThread.ManagedThreadId, msg);

    }

    先看结果:

    1: test: await之前

    1: Main:调用test后

    3: awaited Task1执行

    4: awaited Task2执行

    4: awaited Task3执行

    doo结果:6

    4: test: await之后

    ThreadPool.QueueUserWorkItem

    Task.Run

    和上一个返回Task的例子一样,当在test方法中await doo方法返回的Task,doo内awaited Task都被先等了,而没有awaited的线程都并没有被等,这是为什么呢(也就是上面留下的那个问题)?下面用这个返回Task<int>的例子解释一下:

    在test中await doo返回的Task,那么此时我们需要他的结果,而他的结果是需要自己方法内所包含的其他awaited结果,可以理解成被等的子结果。所以自己的结果需要其他的结果,那么等这个结果必须需要等那些被依赖的结果也出来。所以test方法await doo方法的结果会同样等待所有doo内的await,不会管其他doo内非await的多线程执行(当然从技术角度讲,也是不可能的,因为async/await可以这样全靠的是编译器)。
     


    摘自 Mgen

  • 相关阅读:
    二叉排序树(B-Tree)-c实现
    队列(Queue)-c实现
    栈(stack)--c实现(使用双链表)
    链表(list)--c实现
    c 和 指针读书笔记(1)
    c traps and pitfalls reading notes(2)
    js 控制
    正则表达式
    Android 笔记
    Android 布局方式学习
  • 原文地址:https://www.cnblogs.com/webenh/p/6127922.html
Copyright © 2011-2022 走看看