总目录
文章目录
- 总目录
- 前言
- 一、概述
- 二、命名规范
- 三、await/async的作用
- 四、基本使用
- 五、使用Async和Await实现多任务顺序执行且不阻塞
- 1.同步执行
- 2.并行执行
- 3.并行且可指定顺序执行
- 总结
前言
C# 中的 Async 和 Await 关键字是异步编程的核心。 通过这两个关键字,可以使用 .NET Framework、.NET Core 或 Windows 运行时中的资源,轻松创建异步方法(几乎与创建同步方法一样轻松)。
一、概述
- 使用 async 修饰符可将方法、lambda 表达式或匿名方法指定为异步。 如果对方法或表达式使用此修饰符,则其称为异步方法。
- async 关键字修饰的方法一般包含一个或多个await 表达式或语句,如果不包含 await 表达式或语句,则该方法将同步执行。 编译器警告将通知你不包含 await 语句的任何异步方法。
- async 关键字是上下文关键字,原因在于只有当它修饰方法、lambda 表达式或匿名方法时,它才是关键字。 在所有其他上下文中,都会将其解释为标识符。
- 只能在通过 async 关键字修饰的方法、lambda 表达式或匿名方法中使用 await 运算符
- 异步方法既不能声明任何 in、ref 或 out 参数,也不能具有引用返回(ref 返回)值,但它可以调用具有此类参数的方法。
- 使用async修饰的方法,async需位于返回类型前,如:public async void CountAsync()
- 异步方法的返回类型必须是 void 或任务类型。 任务类型是 System.Threading.Tasks.Task 和构造自的类型 System.Threading.Tasks.Task<T>。
- 对于除事件处理程序以外的代码,通常不鼓励使用 async void 方法,因为调用方不能 await 那些方法,并且必须实现不同的机制来报告成功完成或错误条件。
- await 运算符的操作数通常是以下其中一个 .NET 类型:Task、Task<TResult>、ValueTask 或 ValueTask。 但是,任何可等待表达式都可以是 await 运算符的操作数。
二、命名规范
- 按照约定,返回常规可等待类型的方法(例如 Task、Task<T>、ValueTask 和 ValueTask<T>)应具有以“Async”结束的名称。
- 启动异步操作但不返回可等待类型的方法不得具有以“Async”结尾的名称,但其开头可以为“Begin”、“Start”或其他表明此方法不返回或引发操作结果的动词。
三、await/async的作用
await/async可以简化我们异步编程的代码,也让我们可以以一种类似同步编程的方式来进行异步编程,另外当我们需要不阻塞主线程异步执行,又要有顺序的执行相关代码的时候,await/async就可以排上用场。
四、基本使用
第一种情况:只有async ,没有await,有或无返回值
这种情况对于异步编程没什么意义,可以看作是一个同步方法,因为async和await一起使用才有意义。
第二种情况:async和await配套使用,返回值为void
在WinForm窗体中放置一个按钮,点击按钮,代码如下:
private void button2_Click(object sender, EventArgs e)
{
Debug.WriteLine("主线程--开始");
TestVoidAsync();
Debug.WriteLine("主线程--结束");
}
private async void TestVoidAsync()
{
Debug.WriteLine("开始执行TestVoidAsync方法");
Task task = new Task(() =>
{
Debug.WriteLine("开始子线程耗时操作");
Thread.Sleep(4000);
Debug.WriteLine("结束子线程耗时操作");
});
task.Start();
await task;
Debug.WriteLine("await关键字后面的内容 1");
Debug.WriteLine("await关键字后面的内容 2");
}
我们会发现,使用await后,执行到await,主线程就是返回去做自己的事情,而await后面的内容将会在子线程执行完成后再继续完成。
当我们运行上面这两这个方法,可以方法运行结果基本一样,从这里看出await的作用和ContinueWith的作用类似,可以在上一任务以后接下一个任务,并且不会阻塞主线程。从上面案例来说,就是不会去卡界面。
另外需要注意:执行await后面的代码的线程可能是执行task的线程,也有可能是新开的线程,也有可能是主线程,这个是不确定的。
第三种情况:async和await配套使用,返回值Task
- async Task 等效于 async void
- 但是返回Task ,可以使用await,并且可以与Task.WhenAny, Task.WhenAll ContinueWith等组合使用,Void不可以
- 建议使用async Task 这种形式(除事件处理程序外)
写法与async void 相比,没有什么太大的不一样,只是将void ,更换为Task而已,但是返回Task,可以使用await 以及一些Task的方法,如Task.WhenAny, Task.WhenAll ContinueWith等,更有利于后续逻辑的扩展。
如:我们可以在返回Task的方法执行完后使用ContinueWith 再接一个执行任务。
public void TestTask()
{
var task = TestTaskAsync();
task.ContinueWith((t) =>
{
Debug.WriteLine("这里是接着TestTaskAsync方法的执行代码。。。");
});
}
第四种情况:async和await配套使用,返回值Task<T>
- 如果async和await配套使用的时候,需要返回值,则必须通过Task<T>的形式返回值
- 如果不是,编译器会操作,如下图所示
private async Task<int> TestTaskIntAsync()
{
Debug.WriteLine("开始执行TestTaskAsync方法");
int result = 0;
Task task = new Task(() =>
{
Debug.WriteLine("开始子线程耗时操作");
Thread.Sleep(4000);
Debug.WriteLine("结束子线程耗时操作");
result = 10;
});
task.Start();
await task;
Debug.WriteLine("await关键字后面的内容 1");
Debug.WriteLine("await关键字后面的内容 2");
return result;
}
该方法与我们平常返回一个int值的方法没什么太大区别,不过是多了async 和 await ,那么下面将演示如何取出异步方法的返回值。
//方法一:使用ContinueWith
private void button4_Click(object sender, EventArgs e)
{
Task<int> task = TestTaskIntAsync();
task.ContinueWith((t) =>
{
Debug.WriteLine($"TestTaskIntAsync的返回值是:{t.Result.ToString()}");
});
}
//方法二:使用await
private async void button4_Click(object sender, EventArgs e)
{
Task<int> task = TestTaskIntAsync();
await task;
Debug.WriteLine($"TestTaskIntAsync的返回值是:{task.Result.ToString()}");
}
第五种情况:不使用async和await,返回值Task<T>
- 之所以列举出来是需要注意区别使用async和await 返回的Task<T> 和不使用async和await 返回Task<T>
private Task<int> TestTaskInt()
{
Debug.WriteLine("开始执行TestTaskAsync方法");
Task<int> task = new Task<int>(() =>
{
Debug.WriteLine("开始子线程耗时操作");
Thread.Sleep(4000);
Debug.WriteLine("结束子线程耗时操作");
int result = 10;//模拟有返回值
return result;
});
task.Start();
return task;
}
//调用,获取返回值
private void button4_Click(object sender, EventArgs e)
{
var task = TestTaskInt();
task.ContinueWith((t)=>
{
Debug.WriteLine(t.Result.ToString());
});
}
五、使用Async和Await实现多任务顺序执行且不阻塞
以微软文档的做早餐的案例加以简化来讲解
1.同步执行
using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadTest
{
class Program
{
static void Main(string[] args)
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
PourOJ();
PourCoffee();
ToastBread();
FryBacon();
FryEggs();
Console.WriteLine("早餐已经做完!");
stopwatch.Stop();
Console.WriteLine($"做早餐总计耗时:{stopwatch.ElapsedMilliseconds}");
Console.ReadLine();
}
//倒橙汁
private static void PourOJ()
{
Thread.Sleep(1000);
Console.WriteLine("倒一杯橙汁");
}
//烤面包
private static void ToastBread()
{
Console.WriteLine("开始烤面包");
Thread.Sleep(3000);
Console.WriteLine("烤面包好了");
}
//煎培根
private static void FryBacon()
{
Console.WriteLine("开始煎培根");
Thread.Sleep(6000);
Console.WriteLine("培根煎好了");
}
//煎鸡蛋
private static void FryEggs()
{
Console.WriteLine("开始煎鸡蛋");
Thread.Sleep(6000);
Console.WriteLine("鸡蛋好了");
}
//倒咖啡
private static void PourCoffee()
{
Thread.Sleep(1000);
Console.WriteLine("倒咖啡");
}
}
}
以上案例同步执行,对于做早餐来说是非常的耗时的。
2.并行执行
如果此时我们每一项任务都有一个单独的人去完成
那么可以如下:
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace ThreadTest
{
class Program
{
static void Main(string[] args)
{
Test();
Console.ReadLine();
}
private static void Test()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
List<Task> tasks = new List<Task>() { PourOJ(), ToastBread(), FryBacon(), FryEggs(), PourCoffee() };
Task.WhenAll(tasks).ContinueWith((t)=>
{
Console.WriteLine("早餐已经做完!");
stopwatch.Stop();
Console.WriteLine($"做早餐总计耗时:{stopwatch.ElapsedMilliseconds}");
});
}
//倒橙汁
private static async Task PourOJ()
{
await Task.Delay(1000);
Console.WriteLine("倒一杯橙汁");
}
//烤面包
private static async Task ToastBread()
{
Console.WriteLine("开始烤面包");
await Task.Delay(3000);
Console.WriteLine("烤面包好了");
}
//煎培根
private static async Task FryBacon()
{
Console.WriteLine("开始煎培根");
await Task.Delay(6000);
Console.WriteLine("培根煎好了");
}
//煎鸡蛋
private static async Task FryEggs()
{
Console.WriteLine("开始煎鸡蛋");
await Task.Delay(6000);
Console.WriteLine("鸡蛋好了");
}
//倒咖啡
private static async Task PourCoffee()
{
await Task.Delay(1000);
Console.WriteLine("倒咖啡");
}
}
}
可以看出来,由于所有的任务都并行执行的,因此做早餐的时间,明显的减少了。
3.并行且可指定顺序执行
现在呢,有个问题,不可能每次做早餐你都有那么多帮手,同时帮你,如果现在要求,先倒橙汁,然后倒咖啡,其余的操作并行执行,应该如何操作呢?
只需将以上案例的Test 方法修改如下:
private static async void Test()
{
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
await PourOJ();
await PourCoffee();
List<Task> tasks = new List<Task>() { ToastBread(), FryBacon(), FryEggs() };
await Task.WhenAll(tasks);
Console.WriteLine("早餐已经做完!");
stopwatch.Stop();
Console.WriteLine($"做早餐总计耗时:{stopwatch.ElapsedMilliseconds}");
}
总结
以上就是今天要介绍的内容,希望以上内容可以帮助到大家,如文中有不对之处,还请批评指正。
官方文档-异步编程