异步编程简介
异步编程有两大好处。第一个好处是对于面向终端用户的GUI 程序:异步编程提高了响应能力。我们都遇到过在运行时会临时锁定界面的程序,异步编程可以使程序在执行任务时仍能响应用户的输入。第二个好处是对于服务器端应用:异步编程实现了可扩展性。服务器应用可以利用线程池满足其可扩展性,使用异步编程后,可扩展性通常可以提高一个数量级。
.NET中有三种异步编程的模型接口:
- EAP 是Event-based Asynchronous Pattern(基于事件的异步模型)的简写。
- APM(Asynchronous Programming Model)是.Net旧版本中广泛使用的异步编程模型。
- TPL(Task Parallel Library)是.Net 4.0之后带来的新特性,更简洁,更方便。现在在.Net平台下已经大面积使用。(重点)
1- EAP模式:
示例:
private void button1_Click(object sender, EventArgs e)
{
WebClient wc = new WebClient();
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
wc.DownloadStringCompleted += Wc_DownloadStringCompleted;
wc.DownloadStringAsync(new Uri("https://github.com/github"));
}
private void Wc_DownloadStringCompleted(object sender, DownloadStringCompletedEventArgs e)
{
textBox1.Text = e.Result;
}
说明:某个对象的方法中有以 Async 结尾。并且有异步完成事件,两个条件缺一不可。如上面代码 DownloadStringAsync 是异步下载方法。 wc.DownloadStringCompleted += Wc_DownloadStringCompleted; 注册了异步完成事件,等待异步执行完毕后,自动触发事件响应。
EAP的类的特点是:一个异步方法配一个***Completed事件。.Net中基于EAP的类比较少。也有更好的替代品,因此了解即可。
2- APM模式:
使用了APM的异步方法会返回一个IAsyncResult对象,这个对象有一个重要的属性AsyncWaitHandle,他是一个用来等待异步任务执行结束的一个同步信号。
示例:
private void button1_Click(object sender, EventArgs e)
{
using (FileStream fs = new FileStream("D:/1.txt", FileMode.OpenOrCreate, FileAccess.Read))
{
byte[] buffer = new byte[1024];
//fs.BeginRead():开始异步方法,开始读取文件
IAsyncResult aResult = fs.BeginRead(buffer, 0, buffer.Length, null, null);
aResult.AsyncWaitHandle.WaitOne(); //等待任务执行结束,一定要等待读取完成,否则没有内容
string s = Encoding.Default.GetString(buffer);
// fs.EndRead() 结束异步方法
fs.EndRead(aResult);
this.textBox1.Text = s;
}
}
如果不加aResult.AsyncWaitHandle.WaitOne() 那么很有可能打印出空白,因为BeginRead只是“开始读取”。调用完成一般要调用EndXXX来回收资源。
APM的特点是:方法名字以BeginXXX开头,返回类型为IAsyncResult,调用结束后需要EndXXX。
.Net中有如下的常用类支持APM:Stream、SqlCommand、Socket等。
3-TPL模式:
TPL模式是现在主流的异步模式。
现代的异步.NET 程序使用两个关键字:async 和await。async 关键字加在方法声明上,它的主要目的是使方法内的await 关键字生效(为了保持向后兼容,同时引入了这两个关键字)。如果async 方法有返回值,应返回Task;如果没有返回值,应返回Task。这些task 类型相当于future,用来在异步方法结束时通知主程序。
注意:
不要用void 作为async 方法的返回类型! async 方法可以返回void,但是这仅限于编写事件处理程序。一个普通的async 方法如果没有返回值,要返回Task,而不是void。
示例:
private async void button2_Click(object sender, EventArgs e)
{
//TPL经典写法
//注意方法中如果有await,则方法必须标记为async,不是所有方法都可以被轻松的标记为async。WinForm中的事件处理方法都可以标记为async、MVC中的Action方法也可以标记为async、控制台的Main方法不能标记为async
//TPL的特点是:方法都以XXXAsync结尾,返回值类型是泛型的Task<T>。
using (FileStream fs = File.OpenRead("D:/2.txt"))
{
byte[] buffer = new byte[512];
//await:等待ReadAsync执行结束
await fs.ReadAsync(buffer, 0, buffer.Length);
textBox1.Text = Encoding.Default.GetString(buffer);
}
}
注意点:
如果有await,则方法必须标记为async,不是所有方法都可以被轻松的标记为async。WinForm中的事件处理方法都可以标记为async、MVC中的Action方法也可以标记为async、控制台的Main方法不能标记为async。
什么样的方法能用呢? 方法的返回值必须是 Task< T > 类型的。
示例:
private async void button3_Click(object sender, EventArgs e)
{
WebClient wc = new WebClient();
System.Net.ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12;
string s = await wc.DownloadStringTaskAsync(new Uri("http://www.rupeng.com"));
string s1 = await wc.DownloadStringTaskAsync(new Uri("http://www.github.com"));
textBox1.Text = s1;
textBox2.Text = s;
//虽然没有在单独线程中执行但是并不会导致页面卡顿
}
async 方法在开始时以同步方式执行。在async 方法内部,await 关键字对它的参数执行一个异步等待。它首先检查操作是否已经完成,如果完成了,就继续运行(同步方式)。否则,它会暂停async 方法,并返回,留下一个未完成的task。一段时间后,操作完成,async 方法就恢复运行。
异步方法编写方式:
示例:
返回值为Task,潜规则(不要求)是方法名字以Async结尾:
private Task<string> TestAsync()
{
return Task.Run(() =>
{
Thread.Sleep(3000);
return "你好!";
});
}
/// <summary>
/// 调用异步方法的方法必须标记 async
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void Button7_Click(object sender, EventArgs e)
{
string s = await TestAsync();
MessageBox.Show(s);
}
实例:
在程序用调用Http接口、请求http资源。
使用 HttpClient 类:
HttpClient发出Get请求获取文本响应:string html = await hc.GetStringAsync(“http://www.baidu.com”);
HttpClient发出Post请求使用Task< HttpResponseMessage > PostAsync(string requestUri, HttpContent content) 方法,第一个参数是请求的地址,第二个参数就是用来设置请求内容的。HttpContent是抽象类,主要的子类有FormUrlEncodedContent(表单格式请求)、StringContent(字符串请求)、MultipartFormDataContent(Multipart表单请求,一般带上传文件信息)、StreamContent(流内容)。
表单提交
/// <summary>
///表单提交
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void button4_Click(object sender, EventArgs e)
{
HttpClient hc = new HttpClient();
//提交Form表单请求(post)
//表单参数写法1
List<KeyValuePair<string, string>> paramters = new List<KeyValuePair<string, string>>();
paramters.Add(new KeyValuePair<string, string>("userName", "admin"));
paramters.Add(new KeyValuePair<string, string>("password", "123"));
//表单参数写法2
//Dictionary<string, string> keyValues = new Dictionary<string, string>();
//keyValues["userName"] = "admin";
//keyValues["password"] = "123";
FormUrlEncodedContent content = new FormUrlEncodedContent(paramters);//接受KeyValue对参数
HttpResponseMessage msg=await hc.PostAsync("http://127.0.0.1:8011/Home/Login",content);
//HttpContent con = msg.Content; //返回报文体(包含字符串,流,等)
// msg.Headers //返回响应头,返回System.Net.Http.Headers.HttpResponseHeaders。HTTP响应的集合
// msg.StatusCode//返回响应码
//等等
MessageBox.Show("响应码:" + msg.StatusCode);
string html = await msg.Content.ReadAsStringAsync();
MessageBox.Show(html);
}
上传文件
/// <summary>
/// 上传文件
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private async void button6_Click(object sender, EventArgs e)
{
HttpClient hc = new System.Net.Http.HttpClient();
//提供使用多部分/表单数据MIME类型编码的内容的容器
MultipartFormDataContent content = new MultipartFormDataContent();
//添加报文头内容
content.Headers.Add("UserName", "admin");
content.Headers.Add("Password", "123");
using (FileStream fs = File.OpenRead("C:/Users/wang/Desktop/errorImg.png"))
{
//file 名字必须和MVC方法参数一样
content.Add(new StreamContent(fs), "file", "1.png");
HttpResponseMessage msg = await hc.PostAsync("http://127.0.0.1:8011/Home/Upload", content);
MessageBox.Show("状态码:" + msg.StatusCode);
string html = await msg.Content.ReadAsStringAsync();
MessageBox.Show("返回报文体:" + html);
}
}
发出POST请求,请求体是 JSON格式
private async void button5_Click(object sender, EventArgs e)
{
HttpClient hc = new HttpClient();
string json = "{userName:'admin',password:'123'}";
StringContent content = new StringContent(json); //报文体
//contentype必不可少
content.Headers.ContentType = MediaTypeHeaderValue.Parse("application/json");
//HttpResponseMessage:表示包含状态码和数据的HTTP响应消息。
HttpResponseMessage msg =await hc.PostAsync("http://127.0.0.1:8011/Home/Login2", content);
MessageBox.Show("状态码:" + msg.StatusCode);
string html = await msg.Content.ReadAsStringAsync();
MessageBox.Show("返回报文体:" + html);
}