`
冷寒冰
  • 浏览: 243033 次
  • 性别: Icon_minigender_1
  • 来自: 北京
社区版块
存档分类
最新评论

.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代码做如下警告:



提示: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的测试,呵呵。





或许你会问,为什么要用这样的方式去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可以这样全靠的是编译器)。





:D
分享到:
评论

相关推荐

    ASP.NET 谨用 async/await

    C# 5.0 引入 async/await 关键字,旨在简化异步编程模型,抛去语法糖就是 Net4.0 的 Task + 状态机。其实在处理异步编程使用 Task 还是挺简单的,不过既然推出了新的语法糖,难免会尝试一下,然而在使用中却没想象中...

    C#_5.0使用Task_Await_Async实现异步编程

    自己写的一个winform小示例 用非常简短的几行代码演示C#5.0的异步编程新特性 不参杂其他无关代码,浅显易懂

    多线程下载同一个文件 c# async await

    参照asp.net 上的教程《如何:使用 Task.WhenAll 扩展异步演练 (C#)》,使用了async/await以后,感觉怪怪的。 使用nginx作为服务器,对每个连接限制速度50K。 然同时开启了10个任务进行分段下载(HTTP 1.1支持下载...

    C#基础系列:异步编程初探async和await

    确实,没有异步的多线程是单调的、乏味的,async和await是出现在C#5.0之后,它的出现给了异步并行变成带来了很大的方便。异步编程涉及到的东西还是比较多,本篇还是先介绍下async和await的原理及简单实现。  了解...

    async await 异常处理示例代码

    为文章 Async Await 异常处理 所写,主要展示四种典型情况下的异常处理。 async await; task.wait(); async 不 await; async void

    详解C#中 Thread,Task,Async/Await,IAsyncResult的那些事儿

    本文主要介绍了C#中 Thread,Task,Async/Await,IAsyncResult的相关知识。具有一定的参考价值,下面跟着小编一起来看下吧

    ASP.Net中的async+await异步编程的实现

    在.NET Framework4.5框架、C#5.0语法中,通过async和await两个关键字,引入了一种新的基于任务的异步编程模型(TAP)。在这种方式下,可以通过类似同步方式编写异步代码,极大简化了异步编程模型。 用法: public ...

    C#关键字async/await用法

    使用async方法要定义async Task或者async Task&lt;T&gt; 最好不要定义async void方法来调用,async void是处理程序等,总结结论就是要使用async Task或者async Task 使用asyn方法,可以用同步的格式,执行异步的代码,如下...

    async await 使用demo(WPF C#)

    Example for binding an ObservableCollection to a ListView and using async await Task to update the data

    浅谈C# async await 死锁问题总结

    对异步方法返回的Task调用Wait()或访问Result属性时,可能会产生死锁。 下面的WPF代码会出现死锁: private void Button_Click_7(object sender, RoutedEventArgs e) { Method1().Wait(); } private async Task...

    同步、异步及多线程的使用(Task、Async、Await) WinFormsAsyncAwait.7z

    同步、异步及多线程的使用(Task、Async、Await) Task线程的暂停、继续、取消 Task异步多线程调用中的死锁

    C#中Task.Yield的用途深入讲解

    最近在阅读 .NET Threadpool starvation, and how queuing makes it worse 这篇博文时发现文中代码中的一种 Task 用法之前从未见过,在网上看了一些资料后也是云里雾里不知其解,很是困扰。今天在程序员节的大好日子...

    说说C#的async和await的具体用法

    C# 5.0中引入了async 和 await。这两个关键字可以让你更方便的写出异步代码。 看个例子: public class MyClass { public MyClass() { DisplayValue(); //这里不会阻塞 System.Diagnostics.Debug.WriteLine...

    C#异步并行编程示例

    C#异步编程和并行编程示例,提供了 Thread, Task ,async+await,以及异步Paralle

    聊一聊C# 8.0中的await foreach使用

    或者说,C# 8.0中支持异步返回枚举类型async Task&lt;IEnumerable&gt;&gt;. 好吧,还不懂?Good,这篇文章就是为你写的,看完这篇文章,你就能明白它的神奇之处了. 为什么写这篇文章 Async Streams这个功能已经发布很久了,在去年的...

    C# 异步调用的集中方法

    各种异步方法 包括task、thread、await、async,方法之间的对比

    durabletask:耐用的任务框架允许用户使用asyncawait功能在C#中编写长时间运行的持久性工作流

    耐用任务框架(Durable Task Framework,DTFx)是一个轻量级框架,允许用户使用简单的async / await编码结构在C#中编写长时间运行的持久性工作流(称为业务流程)。 在Microsoft的各个团队中,大量使用它来可靠地...

    C#关于Task.Yeild()函数的讨论

    在与同事讨论async/await内部实现的时候,突然想到Task.Yeild()这个函数,为什么呢,了解一点C#async/await内部机制的都知道,在await一个异步任务(函数)的时候,它会先判断该Task是否已经完成,如果已经完成,则...

    C#异步编程方式以及示例

    这是我自己写的C#异步编程方式以及简单示例: 包含委托、task、await async 方式来实现异步。

Global site tag (gtag.js) - Google Analytics