• 浅析C#中的Thread ThreadPool Task和async/await


    .net 项目中不可避免地要与线程打交道,目的都是实现异步、并发。从最开始的new Thread()入门,到后来的Task.Run(),如今在使用async/await的时候却有很多疑问。

    先来看一段代码:使用Task实现异步

                        Task.Run(() =>
                        {
                            message =  (IBytesMessage)consumer.Receive(m_Interval);
                        }); 

    Receive()方法是一个延迟返回的方法,m_Interval是超时时间。如果采用同步方式执行Receive()的话,那整个程序就会被这个方法堵塞。我个人最习惯的处理方式就用Task.Run()。可惜项目要求必须使用.net framework3.5,所以只能退而求其次,放弃Task,使用Thread或者ThreadPool。

    使用Thread实现异步:

      new Thread(() =>
                        {
                            message =  (IBytesMessage)consumer.Receive(m_Interval);
                        }).Start(); 

    直接new Thread().start()这个写法是很危险的,这里只做参考。在C# 5以后的书籍中,你可能会看到这样一句话:一旦你输入了new Thread(),那就糟糕了,说明项目的代码太过时了。

    使用ThreadPool实现异步:

      
    ThreadPool.QueueUserWorkItem(Listen);  
        
    private void Listen(object state) { message = (IBytesMessage)consumer.Receive(m_Interval); }

    ThreadPool 内部有一套完整的线程管理机制,可以让开发者完全忽略Thread的生命周期控制。但ThreadPool中的线程,都是后台线程,当主线程执行完毕时,程序并不会等待后台线程的执行,而是直接退出。Thread则是前台线程,主程序会等待所有前台线程执行完毕后才会退出。另外在使用ThreadPool的时候需要注意QueueUserWorkItem的参数类型是:

    public delegate void WaitCallback(object state) 所以,Listen方法有一个未用到的参数state。

    综上,Task还是最优的解决方案

    说到这,问题看似解决了,.net 4.0及以上 Task是不二之选,低版本择优先选择ThreadPool,特殊情况考虑Thread。那么 .net4.5的新特性 async/await 有什么用呢?上述情况需要用到async/await 吗?

    这里我们需要看一下完整的代码

     private void Listen(object state)
            {
                message = (IBytesMessage)consumer.Receive(m_Interval);
                if (message != null)
                {
                    m_IAsyncMesssgae.OutputMessage(message.ToString());
                }
                else
                {
                    m_IAsyncMesssgae.OutputException(new Exception("Wait timed out."));
                }
            }
            public void OnStartAsync()
            {
                try
                {
                    if (m_IsConnected && !m_IsListening)
                    {
                        connectionWPM?.Start();
                        m_IsListening = true;
                       ThreadPool.QueueUserWorkItem(Listen);                   
                    }
                }
                catch (Exception ex)
                {
                    m_IAsyncMesssgae.OutputException(ex);
                }
                finally
                {
                    OnStop();
                }
            }

    这里红色字体的m_IAsyncMesssgae是一个回调的接口实例,也就说,此代码中,通过接口回调的方式把Receive()方法延迟返回的message返回给调用者。目前的代码是可以满足需求的。

    我们试着用asynv和await实现一下这个需求。 

     public async void OnStartAsync()
            {
                    if (m_IsConnected && !m_IsListening)
                    {
                        connectionWPM?.Start();
                        m_IsListening = true;
                        message = await Task.Run(()=> {return (IBytesMessage)consumer.Receive(m_Interval); });                  
                    }
            }

    1)async/await 和刚才说的Thread Task ThreadPool并不是一个概念。前者是控制异步和并发的关键字,后者是对线程的三种实现方式。

    2)async/await只能和Task结合使用,async标记的方法 只能有三种返回值Task,Task<T>,void(不建议,因为async/await 就是为了获取异步方法的返回值)。

    3)await等待的内容也必须是Task或者Task<T> 上面代码隐藏了一个内容,其实Task.Run()也是一个返回值为Task<T>的方法。

    4)await还有一个作用是将Task<T>转成T。

    5)在同一个用async标记的方法内,所有在await代码段之后的代码 都要等待await后的内容执行完成后才能执行。

    6)如果一个非async方法 调用async方法获取异步返回值,那么就无法成功获取异步返回值。

    再把返回值void修改一下:

      public async Task<IBytesMessage> OnStartAsync()
            {
                if (m_IsConnected && !m_IsListening)
                {
                    connectionWPM?.Start();
                    m_IsListening = true;
                    message = await Task.Run(()=> {return (IBytesMessage)consumer.Receive(m_Interval); });                  
                }
                return message;
            }

    这样一来,外部调用时候,就不需要接口回调了,直接调用OnStartAsync就可以了。切记!调用OnStartAsync的方法必须也是async,否则就直接返回message的默认值,而不是等待TaskRun()的执行。await只在所属的async方法内奏效。

    调用OnStartAsync也有种不同的写法:

    //写法1
    async  Task  Handle()
            {
                string re = await OnStartAsync();
                 //dosth
            }
    //写法2
    async Task Handle()
           {
                var re = OnStartAsync();
                 //dosth
                do(await re);
           }
    //写法3
    void Handle() { var re = OnStartAsync(); do(re); }
     

     写法1:dosth需要等待 OnStartAsync执行完毕后再执行。

     写法2:dosth先执行,然后再执行do(await re)

     写法3:根本就无法获取正确的返回值,实则没有等待异步执行,而是直接返回了。

    以上,水平有限,如有不足,敬请指正。如有侵权 请联系作者删除。

  • 相关阅读:
    不同的二叉搜索树
    二叉树展开为链表
    二叉树的中序遍历
    二叉树的直径
    树系列之对称二叉树
    从前序与中序遍历序列构造二叉树
    字符串反转
    旋转图像---二维矩阵
    双指针---最接近的三数之和
    Egret 小游戏实战教程 跳一跳(搬运二)
  • 原文地址:https://www.cnblogs.com/nevermore-wd/p/10565639.html
Copyright © 2020-2023  润新知