服务器之家:专注于VPS、云服务器配置技术及软件下载分享
分类导航

PHP教程|ASP.NET教程|Java教程|ASP教程|编程技术|正则表达式|C/C++|IOS|C#|Swift|Android|VB|R语言|JavaScript|易语言|vb.net|

服务器之家 - 编程语言 - C# - C#多线程TPL模式高级用法探秘

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

2023-02-23 15:13.NET开发菜鸟 C#

本文详细讲解了C#多线程TPL模式的高级用法,文中通过示例代码介绍的非常详细。对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下

一、引言

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
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。

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

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
/// <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注释掉,看看异步方法还会不会执行,代码如下:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/// <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的实例。

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

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

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

?
1
2
3
4
5
6
7
8
9
10
11
/// <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属性来获取值,看下面代码:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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()。看下面代码:

?
1
2
3
4
5
6
7
8
9
10
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,看下面的代码:

?
1
2
3
4
5
6
7
8
9
10
11
static async Task<int> Test2Async()
{
    // 复杂写法
    //return await Task.Run<int>(() =>
    //{
    //    return 5;
    //});
 
    // 下面是简化写法,直接返回
    return 6;
}

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

原文链接:https://www.cnblogs.com/dotnet261010/p/12343684.html

延伸 · 阅读

精彩推荐
  • C#英语单词state与status的区别

    英语单词state与status的区别

    state倾向于condition,是一种延续性的状态。status常用于描述一个过程中的某阶段(phase),类似于C语言中枚举型变量某一个固定的值,这个值属于一个已知...

    likebeta8632021-12-09
  • C#C#实现字符串首字母大写的方法示例

    C#实现字符串首字母大写的方法示例

    这篇文章主要给大家介绍了关于利用C#实现字符串首字母大写的相关资料,这是在最近工作中遇到的一个需求,文中通过示例代码介绍的非常详细,对大家...

    lindexi5282022-02-19
  • C#Unity实现物体沿自身的任意轴向旋转

    Unity实现物体沿自身的任意轴向旋转

    这篇文章主要为大家详细介绍了Unity实现物体沿自身的任意轴向旋转,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一...

    xiaochenXIHUA7332022-08-17
  • C#在C#中使用MSMQ的方法

    在C#中使用MSMQ的方法

    这篇文章主要介绍了在C#中使用MSMQ的方法,帮助大家更好的理解和使用c#,感兴趣的朋友可以了解下...

    码农译站3532022-10-31
  • C#Unity shader实现自由放大缩小效果

    Unity shader实现自由放大缩小效果

    这篇文章主要为大家详细介绍了Unity shader实现自由放大缩小效果,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...

    clzmin10052022-03-11
  • C#C#调用usb摄像头的实现方法

    C#调用usb摄像头的实现方法

    这篇文章主要介绍了C#调用usb摄像头的实现方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面...

    Danna_Li10842022-11-01
  • C#Unity3D 计时器的实现代码(三种写法总结)

    Unity3D 计时器的实现代码(三种写法总结)

    这篇文章主要介绍了Unity3D 计时器的实现代码,具有很好的参考价值,希望对大家有所帮助。一起跟随小编过来看看吧...

    加油IT11642022-11-11
  • C#c#枚举值增加特性说明(推荐)

    c#枚举值增加特性说明(推荐)

    这篇文章主要介绍了c#枚举值增加特性说明(推荐),需要的朋友可以参考下...

    冰不化4702022-01-05