async和await关键字可以让你写出和同步代码一样简洁且结构相同的异步代码
awaiting
- await关键字简化了附加continuation的过程。
- 其结构体如下: ```csharp var result = await expression; statement(s);
// 它的作用相等于
var awaiter = expression.GetAwaiter(); awaiter.OnCompleted(()=> { var result = awaiter.GetResult(); statment(s); });
- 例子:
```csharp
class Program
{
static async Task Main(string[] args)
{
await DisplayPrimeCounts();
Console.ReadKey();
}
static async Task DisplayPrimeCounts()
{
int result = await GetPrimesCountAsync(2, 1000000);
Console.WriteLine(result);
}
static Task<int> GetPrimesCountAsync(int start, int count)
{
return Task.Run(() => ParallelEnumerable.Range(start, count)
.Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
}
}
async修饰符
- async修饰符会让编译器把await当作关键字而不是标识符(C#5 以前可能会使用await作为标识符)
- async修饰符只能应用于方法(包括lambda表达式)
- 该方法可以返回void、Task、Task
- 该方法可以返回void、Task、Task
- async修饰符对方法的签名或public元数据没有影响(和unsafe一样),它只会影响方法内部。
- 在接口内使用async是没有意义的。
- 但是,可以使用async来重载非async的方法确实合法的(只要方法签名一致)
- 使用了async修饰符的方法就是“异步函数”
异步方法如何执行
- 遇到await表达式,执行(正常情况下)会返回到调用者那里。
- 就像iterator(迭代器)里面的yield return。
- 在返回之前,运行时会附加一个continuation到await的task
- 为保证task结束时,执行会跳回原方法,从停止的地方继续执行。
- 如果发生故障,那么异常会被重新抛出
- 如果一切正常,那么它的返回值就会赋给await表达式。
可以await什么?
- 你await的表达式通常时一个task
- 也可以满足下列条件的任意对象:
- 有GetAwaiter方法,它返回一个waiter(实现了INotifyCompletion.OnCompleted接口)
- 返回适当类型的GetResult方法
- 一个bool类型的IsCompleted属性
捕获本地状态
- await表达式最牛之处就是几乎可以出现在任何地方。
- 特别的,在异步方法内,await表达式可以替换任何表达式。
- 除了lock表达式和unsafe上下文。
- 例子
class Program
{
static async Task Main(string[] args)
{
await DisplayPrimeCounts();
Console.ReadKey();
}
static async Task DisplayPrimeCounts()
{
for (int i = 0; i < 10; i++)
{
Console.WriteLine(await GetPrimesCountAsync(i * 1000000 + 2, 1000000));
}
}
static Task<int> GetPrimesCountAsync(int start, int count)
{
return Task.Run(() => ParallelEnumerable.Range(start, count)
.Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
}
}
await之后在哪个线程上执行
- 在await表达式之后,编译器依赖于continuation(通过awaiter模式)继续执行
- 如果在富客户端引用的UI线程上,同步上下文会保证后续是在原线程上执行;
- 否则,就会在task结束的线程上继续执行。
UI线程的await
例子
public partial class MainForm : Form
{
public MainForm()
{
InitializeComponent();
}
private void button_Click(object sender, EventArgs e)
{
Go();
}
async void Go()
{
button.Enabled = false;
for (int i = 1; i < 5; i++)
{
txtMsg.Text += await GetPrimesCountAsync(i * 1000000, 1000000) + Environment.NewLine;
}
button.Enabled = true;
}
Task<int> GetPrimesCountAsync(int start, int count)
{
return Task.Run(() => ParallelEnumerable.Range(start, count)
.Count(n => Enumerable.Range(2, (int)Math.Sqrt(n) - 1).All(i => n % i > 0)));
}
}
本例中,只有GetPrimesCountAsync中的代码在worker线程上运行。
- Go中的代码会“租用”UI线程上的时间
- 可以说:Go是消息循环中“伪并发”的执行
- 也就是说:它和UI线程处理的其它事件是穿插执行的
- 因为这种伪并发,唯一能发生“抢占” 时刻就是在await期间。
- 这也就简化了线程安全,防止重新进入即可
- 这种并发发生在调用栈较浅的地方(Task.Run调用的代码里)
- 为了从该模型获益,真正的并发代码要避免访问共享状态或UI控件。
// 伪代码
为本线程设置同步上下文
while(!程序结束)
{
等待消息队列中发生一些事情
发生了事情,是哪种消息?
键盘/鼠标消息 -> 触发evnet handler
用户BeginInvoke/Invoke消息 -> 执行委托
}