目录
一,引言
首先先来区分一下,同步方法,异步方法和多线程:
- 同步方法:调用时需要等待返回结果(相当于阻塞了该线程),才可以继续往下执行业务
- 异步方法:调用时无须等待返回结果(等待时释放线程),可以继续往下执行业务
- 多线程:在主线程之外开启一个新的线程去(增加线程,同步执行)执行业务
关于async/await:
- async和await关键字是C# 5.0时代引入的,它是一种异步编程模型
- 它们本身并不创建新线程,但可以在自行封装的async中利用Task.Run开启新线程
- 方法体中使用await,方法也必须声明为async(成对出现),如果没有await,async关键字也没有意义。
async/await的理解:
个人理解:await可以看着是代码执行的分裂点,当程序执行到await后面的语句时,首先系统会将当前的线程释放(回归线程池,其他程序可调用),并捕获当前上下文(打上标记),进入等待(注意:期间没有线程阻塞)。当await后语句执行完毕,根据标记点调用线程(从线程池随机捕获线程,有概率是原来的线程)执行下面的语句。
二,实例演示
2.1 多线程同步执行下载任务,任务完成后通知
static void Main(string[] args)
{
DownloadHandle();
Console.ReadLine();
}
public static void DownloadHandle()
{
Console.WriteLine("下载开始!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);
var t= Download();
Task.WaitAll(t);
Console.WriteLine("下载完成!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);
}
public static Task Download()
{
return Task.Run(() =>
{
Console.WriteLine("下载线程ID:->" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("10%");
Console.WriteLine("30%");
Console.WriteLine("50%");
Console.WriteLine("60%");
Console.WriteLine("80%");
Console.WriteLine("99%");
Console.WriteLine("100%");
});
}
结果输出:
可以看的,在多线程下载任务时,通过Task.WaitAll(t) 等待线程执行完毕后,主线程一直处于阻塞状态。
2.2 异步执行下载任务,任务完成后通知
static void Main(string[] args)
{
DownloadHandle();
Console.ReadLine();
}
public static async void DownloadHandle()
{
Console.WriteLine("下载开始!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);
await Download();
Console.WriteLine("下载完成!->主线程ID:" + Thread.CurrentThread.ManagedThreadId);
}
public static Task Download()
{
return Task.Run(() =>
{
Console.WriteLine("下载线程ID:->" + Thread.CurrentThread.ManagedThreadId);
Console.WriteLine("10%");
Console.WriteLine("30%");
Console.WriteLine("50%");
Console.WriteLine("60%");
Console.WriteLine("80%");
Console.WriteLine("99%");
Console.WriteLine("100%");
});
}
可以看的,在异步下载任务时,主线程未被阻塞。
异步方法async/await的返回值类型一般都是Task或者Task<T>类型的,当返回值为Task时(即方法的返回值类型为void),我们可以直接return Task.Run(()=>{}),而不必await Task.Run(()=>{}),这样也可从一定程度上提高代码执行效率。另外,不推荐使用async 修饰void返回值,会有异常处理方面的问题。
三,async/await的用法
async/await并不能提升代码的执行速度,但可以提高响应能力(吞吐量),即使用异步方式在同一时间可以处理更多的请求。
- 使用同步方式,线程会被耗时操作一直占用,直到耗时操作结束;
- 使用异步方式,程序走到
await
关键字会立即return
,释放线程,剩下的代码将放到一个回调,耗时操作完成时才会回调执行。因此:
- 对于计算密集型工作,使用多线程
- 对于IO密集型工作,采用异步机制
从代码整体的架构设计来说,由于async/await语法容易使得程序被await传染,因此不要从最里面的方法启动线程,而是把启动线程的代码放到最外面,这样一来绝大部分方法就都不再需要用async修饰了,方法就都可以用正常的方式开发了。
3.1 跨线程修改UI控件
通过async/await的机制,可以非常简洁轻松的实现跨线程修改UI控件的问题,也不用使用Invoke。(因为本质上还是在原来的线程上修改的,还没有阻塞UI界面)
private async void button1_Click(object sender, EventArgs e)
{
var t = Task.Run(() => {
Thread.Sleep(5000);
return "Hello I am TimeConsumingMethod";
});
textBox1.Text = await t;
}
3.2 异步获取数据
在写后端的数据异步处理时,通过async/await语法也可轻易实现,为了防止async/await语法传染,将启动线程的代码放在了最外层,这样在Click事件中就可以正常调用了,不用在增加async关键字了。
private void button2_Click(object sender, EventArgs e)
{
AsyncFunc();
}
private async Task AsyncFunc()
{
DataTable dt = await FecthData();
this.dataGridView1.DataSource = dt;
}
private async Task<DataTable> FecthData()
{
DataTable dt = null;
await Task.Run(() =>
{
dt = new DataTable();
dt.Columns.Add("id", typeof(int));
dt.Columns.Add("name");
for (int i = 0; i < 10000; i++)
{
dt.Rows.Add(new object[] { i, "name" + i.ToString() });
}
Thread.Sleep(1000);
});
return dt;
}