.NET4.0开始引入Task
,它的出现大大简化了异步编程的复杂度,相较于传统的Thread
和ThreadPool
,Task
更加容易控制和使用,下面就来看看它的具体用法。
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
那样执行,就需要使用async
、await
这两个关键字,后面会进行说明。
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开始引入了async
、await
这两个关键字,这两个关键字的出现大大简化了异步编程的难度,使用这两个关键字的时候需要注意: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;
}
}
}
运行之后你会发现:点击按钮一,整个界面处于假死状态,窗体无法拖动,按钮二无法点击
,对于用户来说这显然是无法接受的,因此我们希望的效果是:点击按钮一,窗体仍旧可以拖动,按钮二不受干扰,点击后能弹出提示框
。下面我们就来试试用async
和await
来解决,代码如下:
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;
});
}
}
}
运行之后你会发现:点击按钮一,窗体可以拖动,按钮二可以点击,点击后能弹出对话框
,因此这种情况下我们可以考虑使用async
和await
来提高用户体验。