快速上手C#中的Task编程

.NET4.0开始引入Task,它的出现大大简化了异步编程的复杂度,相较于传统的ThreadThreadPoolTask更加容易控制和使用,下面就来看看它的具体用法。

1、一个简单的串行程序

串行程序大家肯定不陌生,说白了就是从上到下按顺序执行,看下面一段代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            MethodA();
            MethodB();
            Console.WriteLine("Hello World");
            Console.ReadKey();
        }

        static void MethodA()
        {
            Thread.Sleep(4000);
            Console.WriteLine("This is MethodA");
        }

        static void MethodB()
        {
            Thread.Sleep(2000);
            Console.WriteLine("This is MethodB");
        }
    }
}

运行结果如下所示:

This is MethodA
This is MethodB
Hello World

这段代码很简单,首先执行MethodA,然后执行MethodB,最后输出Hello World,想必大家对运行结果应该没什么疑问。但我们也需要考虑一个问题:在串行程序中,如果每个方法都耗时很长时间,一定会让主程序处于卡死状态,那么能否让这些耗时的方法同时执行,使其不影响之后的程序运行呢?。答案当然是可以的,这个时候我们就需要使用Task来解决问题,下面就来看看Task是怎么解决这个问题的。

2、一个简单的Task程序

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Task a = new Task(MethodA);
            Task b = new Task(MethodB);
            a.Start();
            b.Start();
            Console.WriteLine("Hello World");
            Console.ReadKey();
        }

        static void MethodA()
        {
            Thread.Sleep(4000);
            Console.WriteLine("This is MethodA");
        }

        static void MethodB()
        {
            Thread.Sleep(2000);
            Console.WriteLine("This is MethodB");
        }
    }
}

运行结果如下所示:

Hello World
This is MethodB
This is MethodA

是不是感觉很惊喜?从运行结果来看,Task并没有阻碍之后程序的运行,如果遇到一个耗时的方法,那就把它放进Task吧。

3、Task的几种创建方式

创建Task的方式很多,主要就是new Task()Task.Factory.StartNew()Task.Run()这三种方式,下面的代码演示了如何创建Task

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 第一种创建方式
            Task a = new Task(Method);
            a.Start();

            // 第二种创建方式
            Task b = new Task(() =>
            {
                Thread.Sleep(3000);
                Console.WriteLine("This is B");
            });
            b.Start();

            // 第三种创建方式
            Task c = Task.Factory.StartNew(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine("This is C");
            });

            // 第四种创建方式
            Task d = Task.Run(() =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("This is D");
            });

            Console.WriteLine("Hello World");
            Console.ReadKey();
        }

        static void Method()
        {
            Thread.Sleep(4000);
            Console.WriteLine("This is A");
        }
    }
}

运行结果如下所示:

Hello World
This is D
This is C
This is B
This is A

4、创建带返回值的Task

在前面的demo里,我们创建的Task都没有返回值,但有些时候我们也需要创建一些带返回值的Task,下面的代码演示了如何创建带返回值的Task

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 第一种创建方式
            Task<string> a = new Task<string>(() =>
            {
                Thread.Sleep(4000);
                return "This is A";
            });
            a.Start();
            Console.WriteLine(a.Result);

            // 第二种创建方式
            Task<double> b = Task<double>.Factory.StartNew(() =>
            {
                Thread.Sleep(3000);
                return 1;
            });
            Console.WriteLine(b.Result);

            // 第三种创建方式
            Task<bool> c = Task<bool>.Run(() =>
            {
                return DateTime.Now.Year == 2020;
            });
            Console.WriteLine(c.Result);

            Console.WriteLine("Hello World");
            Console.ReadKey();
        }
    }
}

我们一般使用Result属性获取一个Task执行后的结果,运行结果如下所示:

This is A
1
True
Hello World

这里你可能会发现一个问题,我们使用的不是Task吗?为什么这段代码是按照顺序执行的?这好像跟串行程序没什么区别啊!这是因为:task.Result取值的时候,后台线程还没执行完,则会等待其执行完毕!,想让带返回值的Task像不带返回值的Task那样执行,就需要使用asyncawait这两个关键字,后面会进行说明。

5、等待所有任务完成——Task.WaitAll

有时候我们希望等所有的Task执行完毕后再执行之后的代码,这就需要用到Task.WaitAll方法,看下面一段代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 任务a
            Task a = Task.Run(() =>
            {
                Thread.Sleep(4000);
                Console.WriteLine("This is A");
            });

            // 任务b
            Task b = Task.Run(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine("This is B");
            });

            // 等待两个任务完成
            Task.WaitAll(a, b);
            Console.WriteLine("等待全部任务完成才会输出");
            Console.ReadKey();
        }
    }
}

运行结果如下所示:

This is B
This is A
等待全部任务完成才会输出

有时候我们也会给Task.WaitAll方法规定一个时间期限,如果超出了这个时间期限则执行其他代码,在下面这段代码中,规定Task.WaitAll方法的时间期限为3秒,如果超出了3秒,则输出Hello World

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 任务a
            Task a = Task.Run(() =>
            {
                Thread.Sleep(5000);
                Console.WriteLine("This is A");
            });

            // 任务b
            Task b = Task.Run(() =>
            {
                Thread.Sleep(4000);
                Console.WriteLine("This is B");
            });

            // 等待两个任务完成,如果3秒内未完成则输出Hello World
            bool finish = Task.WaitAll(new Task[] { a, b }, 3000);
            if (!finish)
            {
                Console.WriteLine("Hello World");
            }
            Console.ReadKey();
        }
    }
}

运行结果如下所示:

Hello World
This is B
This is A

6、等待其中一个任务完成——Task.WaitAny

Task.WaitAll方法不同,Task.WaitAny方法不需要等待所有任务完成,只要其中的一个任务完成就会继续执行之后的代码,下面这段代码演示了Task.WaitAny的使用方法:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 任务a
            Task a = Task.Run(() =>
            {
                Thread.Sleep(4000);
                Console.WriteLine("This is A");
            });

            // 任务b
            Task b = Task.Run(() =>
            {
                Thread.Sleep(2000);
                Console.WriteLine("This is B");
            });

            // 等待其中一个任务完成
            Task.WaitAny(a, b);
            Console.WriteLine("等待其中一个任务完成就会输出");
            Console.ReadKey();
        }
    }
}

运行结果如下所示:

This is B
等待其中一个任务完成就会输出
This is A

7、按顺序执行Task

有时候我们希望创建的任务按照一定的顺序执行,比如A任务结束后才执行B任务,B任务结束后再执行C任务,这就需要用到ContinueWith方法,看下面一段代码:

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            // 连续任务
            Task task = Task.Run(() =>
            {
                Thread.Sleep(4000);
                Console.WriteLine("This is A");
            })
            .ContinueWith(t =>
            {
                Thread.Sleep(3000);
                Console.WriteLine("This is B");
            })
            .ContinueWith(t =>
            {
                Thread.Sleep(2000);
                Console.WriteLine("This is C");
            })
            .ContinueWith(t =>
            {
                Thread.Sleep(1000);
                Console.WriteLine("This is D");
            });

            Console.WriteLine("Hello World");
            Console.ReadKey();
        }
    }
}

运行结果如下所示:

Hello World
This is A
This is B
This is C
This is D

8、async与await

.NET4.5开始引入了asyncawait这两个关键字,这两个关键字的出现大大简化了异步编程的难度,使用这两个关键字的时候需要注意:async和await成对出现才有意义async只能修饰void、Task、Task<T>这三种返回类型的方法await只能修饰Task和Task<T>

using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Print();
            Console.WriteLine("Hello World");
            Console.ReadKey();
        }

        static async void Print()
        {
            string a = await GetMessageOneAsync();
            string b = await GetMessageTwoAsync();
            Console.WriteLine(a);
            Console.WriteLine(b);
        }

        static async Task<string> GetMessageOneAsync()
        {
            return await Task<string>.Run(() =>
            {
                Thread.Sleep(5000);
                return "This is A";
            });
        }

        static async Task<string> GetMessageTwoAsync()
        {
            return await Task<string>.Run(() =>
            {
                Thread.Sleep(3000);
                return "This is B";
            });
        }
    }
}

运行结果如下所示:

Hello World
This is A
This is B

9、一个WinForm中使用async、await的实例

我在这里做了一个WinForm的demo,界面如下图所示:
在这里插入图片描述
代码如下所示:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // 按钮一
        private void button1_Click(object sender, EventArgs e)
        {
            int a = GetParameterOne();
            int b = GetParameterTwo();
            textBox1.Text = (a + b).ToString();
        }

        // 按钮二
        private void button2_Click(object sender, EventArgs e)
        {
            MessageBox.Show("主线程提示框", "提示", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
        }

        // 假设获取第一个参数花费了5秒
        private int GetParameterOne()
        {
            Thread.Sleep(5000);
            return 20;
        }

        // 假设获取第二个参数花费了3秒
        private int GetParameterTwo()
        {
            Thread.Sleep(3000);
            return 50;
        }
    }
}

运行之后你会发现:点击按钮一,整个界面处于假死状态,窗体无法拖动,按钮二无法点击,对于用户来说这显然是无法接受的,因此我们希望的效果是:点击按钮一,窗体仍旧可以拖动,按钮二不受干扰,点击后能弹出提示框。下面我们就来试试用asyncawait来解决,代码如下:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        // 按钮一
        private async void button1_Click(object sender, EventArgs e)
        {
            int a = await GetParameterOneAsync();
            int b = await GetParameterTwoAsync();
            textBox1.Text = (a + b).ToString();
        }

        // 按钮二
        private void button2_Click(object sender, EventArgs e)
        {
            MessageBox.Show("主线程不受干扰", "提示", MessageBoxButtons.OK, MessageBoxIcon.Asterisk);
        }

        // 假设获取第一个参数花费了5秒
        private async Task<int> GetParameterOneAsync()
        {
            return await Task<int>.Run(() =>
            {
                Thread.Sleep(5000);
                return 20;
            });
        }

        // 假设获取第二个参数花费了3秒
        private async Task<int> GetParameterTwoAsync()
        {
            return await Task<int>.Run(() =>
            {
                Thread.Sleep(3000);
                return 50;
            });
        }
    }
}

运行之后你会发现:点击按钮一,窗体可以拖动,按钮二可以点击,点击后能弹出对话框,因此这种情况下我们可以考虑使用asyncawait来提高用户体验。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章