携手创作,共同成长!这是我参与「掘金日新计划 · 8 月更文挑战」的第25天,点击查看活动详情
上一章中,我们主要介绍了一种分布式模型actor来处理并发。实际中,处理并发的方式有很多种,异步、并行、TPL数据流,响应式等等。本章节主要记录&学习一下异步编程。
异步编程
异步编程首先比较常见的应用场景是GUI里,界面刷新和数据处理是异步操作。实际我们可以把异步编程简单的分为两类:GUI,Server
- 对于GUI程序,异步编程提高了响应能力,很好的解决了界面假死的状态,异步编程可以在程序执行处理计算任务的同时响应用户的操作。
- 对于server应用,异步编程实现了可扩展性,服务端开启线程池满足可扩展性,使用异步编程以后,通常扩展性可以上一个台阶。
C#异步编程
简要
async和await关键字是实现异步编程的关键,两者要成对出现。
- async 用在函数的声明上,他的主要目的是是方法内的await生效。如果async有返回值,应该返回TASk类型,如果没有返回值需要接收,则返回TASK,用来在异步方法结束的时候通知主进程task的状态。
- await 用在函数的内部,如果函数的声明中包含async,而函数内部没有使用await关键字,编译就会报错。
- 一个异步方法里面,可以包含多个await,当程序在await处进行异步等待的时候,会保存当前的运行上下文。
- 如果当前的context不为空,这个上下文就是当前的synchronizationcontext
- 如果当前的context为空,这个上下文就是当前的taskscheduler
- 默认情况下,运行 UI 线程时采用 UI 上下文,处理 ASP .NET 请求时采用 ASP .NET 请求上下文
- 其他很多情况下则采用线程池上下文。
丐版demo
新建一个winfrom程序
程序中,我们在页面上放置两个按钮,两个显示框。
实现简单的功能,点击按钮1,textbox显示hello,点击按钮2,textbox2显示world
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WinFormsAsync
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
/// <summary>
/// button1回调
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "Hello";
}
/// <summary>
/// button2回调
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button2_Click(object sender, EventArgs e)
{
this.textBox2.Text = "world";
}
}
}
修改code
我们修改button1的回调函数,在函数中增加耗时任务。简单的增加一个sleep好了。
/// <summary>
/// button1回调
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "Hello";
for (int loop = 0; loop < 10; loop++)
{
Thread.Sleep(1000);
}
}
我们再运行程序,点击button1后,界面不会刷新,并且在sleep期间,界面也不会响应button2的动作。好了,我的hello world被搞坏了,这就是所谓的界面假死状态。想一想,如果给用户的体验是这个样子,岂不是用户天天要抓狂。。。
异步
我们使用异步方法,把上面的耗时任务异步处理,使用async和awiait优化code如下;
/// <summary>
/// 异步耗时任务
/// </summary>
private async void BackTaskAsync()
{
await Task.Run(() =>
{
for (int loop = 0; loop < 10; loop++)
{
Thread.Sleep(1000);
}
MessageBox.Show("Async Over");
});
}
/// <summary>
/// button1回调
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "Hello";
BackTaskAsync();
}
这次,我们再点击button1,hello可以立刻显示,并且同时button2也不受影响的进行操作。奈斯,我的hello world又回来了。
在我们操作一番后,异步的task接收messagebox才会弹出。
异步更新
我们再次修改code,让异步的code耗时任务更新界面UI,前面我们介绍过,异步的gui中,获取的上下文是gui上下文,所以我们可以异步的去更新gui。
/// <summary>
/// 异步耗时任务
/// </summary>
private async void BackTaskAsync()
{
var res = await Task.Run(() =>
{
for (int loop = 0; loop < 10; loop++)
{
Thread.Sleep(1000);
}
return "Task Async Over";
});
this.textBox1.Text = res;
}
/// <summary>
/// button1回调
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void button1_Click(object sender, EventArgs e)
{
this.textBox1.Text = "Hello";
BackTaskAsync();
}
以上代码的执行结果是,button1先显示hello,最后,异步执行完毕后,textbox1变成task async over。
这就是丐版的异步demo。
关于异步的其他高级用法和场景大家可以继续发掘研究、这里仅浅谈一下。