C#多线程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()方法内部。

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

  • 只要方法是Task<T>类型的返回值,都可以用await来等待调用获取返回值。
  • 如果一个返回Task<T>类型的方法被标记了async,那么只要方法内部直接return T这个类型的实例就可以了。
  • 一个返回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;
}

到此这篇关于C#多线程TPL模式高级用法探秘的文章就介绍到这了。希望对大家的学习有所帮助,也希望大家多多支持我们。

(0)

相关推荐

  • ASP.NET MVC使用异步TPL模式

    1.MVC中如何使用异步 我们新建一个MVC的项目,我们在Home控制器的Index方法里面读取一个文件的内容,然后返回给用户,我们看下面的代码: /// <summary> /// 异步方法 /// </summary> /// <returns></returns> public Task<ActionResult> Index() { return Task.Run<ActionResult>(() => { using

  • C#多线程TPL常见操作误区与异常处理

    一.一定要async到底 一定要让async的传染性(调用异步方法要用await,用了await的方法就要声明为async,调用我这个async方法的地方必须要await.....)不要轻易直接调用Task的Wait.WaitAll等方法.等待一个用await,而不是task.Wait():等待多个用await Task.WhenAll(),而不是Task.WaitAll(). 二.用了异步性能不一定高 程序里面使用了异步不一定说性能就一定高,下面的异步方法就没有意义: public async

  • C#多线程TPL模式下使用HttpClient

    一.引言 我们有时侯需要在程序里面调用Http接口.请求http资源.编写http爬虫等的时候都需要在程序里面进行Http请求.很多人习惯的WebClient.HttpWebRequest在TPL下有很多用起来不方便的地方,TPL下推荐使用HttpClient(using System.Net.Http),而且在.NET Core下已经不在支持WebClient等. 1.发送Get请求 HttpClient发出Get请求获取文本响应,如下面的代码: // 实例化HttpClient对象 Http

  • C#多线程TPL模式高级用法探秘

    一.引言 我们先来看下面的一个小示例:一个Winfrom程序,界面上有一个按钮,有两个异步方法,点击按钮调用两个异步方法,弹出执行顺序,代码如下: using System; using System.Threading; using System.Threading.Tasks; using System.Windows.Forms; namespace TPLDemoSln { public partial class Form1 : Form { public Form1() { Init

  • Linux中mv命令的高级用法示例

    前言 mv命令是move的缩写,可以用来移动文件或者将文件改名(move (rename) files),是Linux系统下常用的命令,经常用来备份文件或者目录. 命令格式: mv [选项] 源文件或目录 目标文件或目录 mv 也是 Linux 下一个使用频率非常高的命令,但除了一些基本用法,你还知道它的哪些高级用法呢? 1. 基本用法 移动一个/多个文件: 移动一个/多个目录: 重命名文件/目录. 这些都是很基本的用法,无需赘述.下面介绍一些更高级的用法. 2. 打印操作信息 我们如果只移动一

  • 详解shell 变量的高级用法示例

    变量删除和替换 案例:从头开始匹配,将符合最短的数据删除 (#) variable_1="I love you, Do you love me" echo $variable_1 variable_2=${variable_1#*ov} echo $variable_2 案例:从头开始匹配,将复合最短的数据删除(##) varible_3=${variable_1##*ov} echo $varible_3 案例:替换字符串,只替换第一次匹配成功的(/) echo $PATH var6

  • 深入了解Python装饰器的高级用法

    原文地址 https://www.codementor.io/python/tutorial/advanced-use-python-decorators-class-function 介绍 我写这篇文章的主要目的是介绍装饰器的高级用法.如果你对装饰器知之甚少,或者对本文讲到的知识点易混淆.我建议你复习下装饰器基础教程. 本教程的目标是介绍装饰器的一些有趣的用法.特别是怎样在类中使用装饰器,怎样给装饰器传递额外的参数. 装饰器 vs 装饰器模式 Decorator模式是一个面向对象的设计模式,它

  • Shell编程之变量的高级用法详解

    变量替换 语法 说明 ${变量名#匹配规则} 从变量开头进行规则匹配,将符合最短的数据删除 ${变量名##匹配规则} 从变量开头进行规则匹配,将符合最长的数据删除 ${变量名%匹配规则} 从变量尾部进行规则匹配,将符合最短的数据删除 ${变量名%%匹配规则} 从变量尾部进行规则匹配,将符合最长的数据删除 ${变量名/旧字符串/新字符串} 变量内容符合就字符串,则第一个旧字符串会被新字符串替换 ${变量名//旧字符串/新字符串} 变量内容符合就字符串,则全部的旧字符串会被新字符串替换 示例 var

  • gojs一些实用的高级用法

    目录 1. 取消更新动画 2. 导出图(含可视区外的部分) 3. 禁用 ctrl 相关快捷键 4. 画布滚动模式,无限滚动 or 局部滚动 5. 展开收起多层嵌套的组 6. 给图元素加动画 7. 修改框选的样式 1. 取消更新动画 问题:更新数据的时候,会触发渲染,有渲染动画,用户体验不好. 方案:初始数据绘制,有动画:更新数据绘制,无动画. 代码实现: // 后面所用到的 diagram 都是 gojs 创建的实例 // diagram_container 为图容器dom id diagram

  • 分享JavaScript的 3 种工厂模式的用法

    目录 一.简单工厂模式 二.工厂方法模式 抽象工厂模式 三.小结 前言; 工厂模式(Factory Pattern)是设计模式中最常用的设计模式之一. 这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式. 在工厂模式中,我们在创建对象时不会对客户端暴露创建逻辑,并且是通过使用一个共同的接口来指向新创建的对象. 工厂模式分为: 简单工厂模式 工厂方法模式 抽象工厂模式 一.简单工厂模式 简单工厂模式,也可以叫静态工厂模式,用一个工厂对象创建同一类对象类的实例. 比如: // 0.0.

  • java高级用法之JNA中的回调问题

    目录 简介 JNA中的Callback callback的应用 callback的定义 callback的获取和应用 在多线程环境中使用callback 总结 简介 什么是callback呢?简单点说callback就是回调通知,当我们需要在某个方法完成之后,或者某个事件触发之后,来通知进行某些特定的任务就需要用到callback了. 最有可能看到callback的语言就是javascript了,基本上在javascript中,callback无处不在.为了解决callback导致的回调地狱的问

随机推荐