zoukankan      html  css  js  c++  java
  • TPL高级探秘

    一、引言

    我们先来看下面的一个小示例:一个Winfrom程序,界面上有一个按钮,有两个异步方法,点击按钮调用两个异步方法,弹出执行顺序,代码如下:

    using System;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;
    
    namespace TPLDemoSln
    {
        public partial class Form1 : Form
        {
            public Form1()
            {
                InitializeComponent();
            }
    
            /// <summary>
            /// 按钮点击事件
            /// </summary>
            /// <param name="sender"></param>
            /// <param name="e"></param>
            private async void btnStart_Click(object sender, EventArgs e)
            {
                string i1 = await F1Async();
                MessageBox.Show("i1=" + i1);
                string i2 = await F2Async();
                MessageBox.Show("i2=" + i2);
            }
    
            /// <summary>
            /// 异步方法F1
            /// </summary>
            /// <returns></returns>
            private Task<string> F1Async()
            {
                MessageBox.Show("F1 Start");
                return Task.Run<string>(() => 
                {
                    // 休眠1秒
                    Thread.Sleep(1000);
                    MessageBox.Show("F1 Run");
                    return "F1";
                });
            }
    
            /// <summary>
            /// 异步方法F2
            /// </summary>
            /// <returns></returns>
            private Task<string> F2Async()
            {
                MessageBox.Show("F2 Start");
                return Task.Run<string>(() =>
                {
                    // 休眠2秒
                    Thread.Sleep(2000);
                    MessageBox.Show("F2 Run");
                    return "F2";
                });
            }
        }
    }

    在上面的代码中,Task.Run()是用来把一个代码段包装为Task<T>的方法,Run中委托的代码体就是异步任务执行的逻辑,最后return返回值。

    运行程序,可以得到如下的输出顺序:

    F1 Start->F1 Run->i1=F1->F2 Start->F2 Run->i2=F2。

    我们对按钮事件进行修改,修改为下面的代码,在看看执行顺序:

    /// <summary>
    /// 按钮点击事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private async void btnStart_Click(object sender, EventArgs e)
    {
        //string i1 = await F1Async();
        //MessageBox.Show("i1=" + i1);
        //string i2 = await F2Async();
        //MessageBox.Show("i2=" + i2);
    
        Task<string> task1 = F1Async();
        Task<string> task2 = F2Async();
        string i1 = await task1;
        MessageBox.Show("i1=" + i1);
        string i2 = await task2;
        MessageBox.Show("i2=" + i2);
    }

    再次运行程序,查看输出顺序:

    F1 Start->F2 Start->F1 Run->i1=F1->F2 Run->i2=F2。

    可以看出两次的执行顺序不一致。这是什么原因呢?

    这是因为并不是到了await才开始执行Task异步任务,执行到Task<string> task1=F1Async()这句代码的时候,F1Async异步任务就开始执行了。同理,执行到下一句代码就开始执行F2Async异步任务了。await是为了保证执行到这里的时候异步任务一定执行完。执行到await的时候,如果异步任务还没有执行,那么就等待异步任务执行完。如果异步任务已经执行完了,那么就直接获取异步任务的返回值。

    我们上面解释的原因是否正确呢?我们可以做下面的一个实验,来验证上面说的原因,我们把按钮事件里面的await注释掉,看看异步方法还会不会执行,代码如下:

    /// <summary>
    /// 按钮点击事件
    /// </summary>
    /// <param name="sender"></param>
    /// <param name="e"></param>
    private async void btnStart_Click(object sender, EventArgs e)
    {
        //string i1 = await F1Async();
        //MessageBox.Show("i1=" + i1);
        //string i2 = await F2Async();
        //MessageBox.Show("i2=" + i2);
    
        Task<string> task1 = F1Async();
        Task<string> task2 = F2Async();
        // 并不是到了await才开始执行Task异步任务,这里是保证异步任务一定执行完
        // 代码执行到这里的时候,如果没有执行完就等待执行完,如果已经执行完,就直接获取返回值
        // 这里注释掉await代码段,查看异步方法是否会执行
        //string i1 = await task1;
        //MessageBox.Show("i1=" + i1);
        //string i2 = await task2;
        //MessageBox.Show("i2=" + i2);
    }

    运行程序,发现异步方法还是会执行,这就说明我们上面解释的原因是正确的。感兴趣的可以使用Reflector反编译查看内部实现的原理,主要是MoveNext()方法内部。

    我们可以得到下面的结论:

    1. 只要方法是Task<T>类型的返回值,都可以用await来等待调用获取返回值。
    2. 如果一个返回Task<T>类型的方法被标记了async,那么只要方法内部直接return T这个类型的实例就可以了。
    3. 一个返回Task<T>类型的方法如果没有被标记为async,那么需要方法内部直接return一个Task的实例。

    上面说的第二点看下面的代码

    /// <summary>
    /// 方法标记为async 直接返回一个int类型的数值即可
    /// </summary>
    /// <returns></returns>
    private async Task<int> F3Async()
    {
        return 2;
    }

    上面的第三点可以看下面的代码:

    /// <summary>
    /// 方法没有被标记为async,直接返回一个Task
    /// </summary>
    /// <returns></returns>
    private Task<int> F4Async()
    {
        return Task.Run<int>(() => 
        {
            return 2;
        });
    }

    二、TPL高级

    我们做一些总结:

    1、如果方法内部有await,则方法必须标记为async。await和async是成对出现的,只有await没有async程序会报错。只有async没有await,程序会按照同步方法执行。

    2、ASP.NET MVC中的Action方法和WinForm中的事件处理方法都可以标记为async,控制台的Main()方法不能被标记为async。对于不能标记为async的方法怎么办呢?我们可以使用Result属性来获取值,看下面代码:

    using System;
    using System.Net.Http;
    using System.Threading.Tasks;
    
    namespace AsyncDemo
    {
        class Program
        {
            static void Main(string[] args)
            {
                // 实例化对象
                HttpClient client = new HttpClient();
                // 调用异步的Get方法
                Task<HttpResponseMessage> taskMsg = client.GetAsync("http://www.baidu.com");
                // 通过Result属性获取返回值
                HttpResponseMessage msg = taskMsg.Result;
                Task<string> taskRead = msg.Content.ReadAsStringAsync();
                string html = taskRead.Result;
                Console.WriteLine(html);
                Console.ReadKey();
            }
        }
    }

    不建议使用这种方式,这样体现不出异步带来的好处,而且使用Result属性,有可能会带来上下文切换造成的死锁。

    下面我们来看看创建Task的方法。

     1、如果返回值就是一个立即可以随手得到的值,那么就用Task.FromResult()。看下面代码:

    static Task<int> TestAsync()
    {
        //return Task.Run<int>(() => 
        //{
        //    return 5;
        //});
    
        // 简便写法
        return Task.FromResult(3);
    }

    2、如果是一个需要休息一会的任务(比如下载失败则过5秒钟后重试。主线程不休息,和Thread.Sleep不一样),那么就用Task.Delay()。

    3、Task.Factory.FromAsync()会把IAsyncResult转换为Task,这样APM风格的API也可以用await来调用。

    4、编写异步方法的简化写法。如果方法声明为async,那么可以直接return具体的值,不用在创建Task,由编译器创建Task,看下面的代码:

    static async Task<int> Test2Async()
    {
        // 复杂写法
        //return await Task.Run<int>(() => 
        //{
        //    return 5;
        //});
    
        // 下面是简化写法,直接返回
        return 6;
    }
  • 相关阅读:
    Linux开机启动详解
    git配置多用户多平台
    CentOS7 启动docker.service失败(code=exited, status=1/FAILURE)
    Linux 利用lsof命令恢复删除的文件
    56.storm 之 hello world (集群模式)
    55.storm 之 hello word(本地模式)
    54.Storm环境搭建
    53.storm简介
    深入浅出Mybatis-分页
    storm:最火的流式处理框架
  • 原文地址:https://www.cnblogs.com/dotnet261010/p/12343684.html
Copyright © 2011-2022 走看看