目录
1.异步定义
2.async/await构建异步方法
2.1直观认识
2.2 异步方法详解
2.3异步操作的取消
3.使用BackgroundWorker类构建异步方法
3.并行循环
4.传统旧模式
异步程序中,程序代码不需要按照编写时的顺序严格执行。为了改善代码性能,有时需要在一个新的线程中运行一部分代码,有时无需创建新的线程,但为了更好的利用单个线程的能力,需要改变代码的执行顺序。
简而言之:异步编程提供代码的非顺序执行能力。让程序能够在部分耗时操作的同时,干其它的事情。
async/await构建异步方法是C# 5.0引入的新特性。是几种异步方法中最简练的一种。
示例代码:
//异步操作示例类
using System;
using System.Threading.Tasks;
using System.Net;
using System.Diagnostics;
namespace ASyncTest
{
class MyDownloadString
{
Stopwatch st = new Stopwatch();
Stopwatch st1 = new Stopwatch();
///
/// 普通方式
///
public void DoRun()
{
const int LargeNum = 600000;
st.Start();
int t1 = CountCharacters(1, "http://www.microsoft.com");
int t2 = CountCharacters(2, "http://www.csdn.net");
CountToALargeNumber(1, LargeNum);
CountToALargeNumber(2, LargeNum);
CountToALargeNumber(3, LargeNum);
CountToALargeNumber(4, LargeNum);
Console.WriteLine("http://www.microsoft.com 网站字符数:{0}", t1);
Console.WriteLine("http://www.csdn.net 网站字符数:{0}", t2);
st.Stop();
}
///
/// 异步方式
///
public void DoRunAsync()
{
const int LargeNum = 600000;
st1.Start();
Task
Task
CountToALargeNumber2(1, LargeNum);
CountToALargeNumber2(2, LargeNum);
CountToALargeNumber2(3, LargeNum);
CountToALargeNumber2(4, LargeNum);
Console.WriteLine("http://www.microsoft.com 网站字符数:{0}", t1.Result);
Console.WriteLine("http://www.csdn.net 网站字符数:{0}", t2.Result);
}
private void CountToALargeNumber(int id, int largeNum)
{
for (long i = 0; i < largeNum; i++)
{ }
Console.WriteLine(" 结束计数ID{0}: {1,4:N0}ms", id, st.Elapsed.TotalMilliseconds);
}
private int CountCharacters(int id, string uriString)
{
WebClient wc1 = new WebClient();
Console.WriteLine("开始调用{0}: {1,4:N0} ms", id, st.Elapsed.TotalMilliseconds);
string result = wc1.DownloadString(new Uri(uriString));
Console.WriteLine(" 调用{0}完成:{1,4:N0} ms", id, st.Elapsed.TotalMilliseconds);
return result.Length;
}
private async Task
{
WebClient wc1 = new WebClient();
Console.WriteLine("开始调用{0}: {1,4:N0} ms", id, st1.Elapsed.TotalMilliseconds);
string result =await wc1.DownloadStringTaskAsync(new Uri(uriString));
Console.WriteLine(" 调用{0}完成:{1,4:N0} ms", id, st1.Elapsed.TotalMilliseconds);
return result.Length;
}
private void CountToALargeNumber2(int id, int largeNum)
{
for (long i = 0; i < largeNum; i++)
{ }
Console.WriteLine(" 结束计数ID{0}: {1,4:N0}ms", id, st1.Elapsed.TotalMilliseconds);
}
}
}
========================================
using System;
namespace ASyncTest
{
class Program
{
static void Main(string[] args)
{
MyDownloadString mds = new MyDownloadString();
mds.DoRun();
Console.WriteLine("==========================================");
mds.DoRunAsync();
Console.ReadKey();
}
}
}
异步方式耗时约为普通方式的1/2以下(不同运行存在差异)。
异步方法在完成其工作之前就返回到调用方法,然后在调用方法继续执行其他部分的时候完成工作。
异步方法具备的特点:
async/await特性更适合在hout后台完成的小任务。
有时候我们需要在后台新建一个线程默默完成一项工作,过程中时不时同主线程进行通信,这就是BackgroundWorker类的主要任务。
BackgroundWorker类的使用比较简单。参考样例即可。
======================================================
using System;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;
namespace BackgroundWorkTest
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
BackgroundWorker bw = (sender as BackgroundWorker);
for(int i=1;i<11;i++)
{
if (bw.CancellationPending)
{
e.Cancel = true;
break;
}
else
{
bw.ReportProgress(i * 10);
Thread.Sleep(500);
}
}
}
private void backgroundWorker1_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
progressBar1.Value = e.ProgressPercentage;
}
private void backgroundWorker1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
progressBar1.Value = 0;
if(e.Cancelled)
{
MessageBox.Show("进程被取消", "取消进程");
}
else
{
MessageBox.Show("进程完成", "完成");
}
}
private void button1_Click(object sender, EventArgs e)
{
if(!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
}
}
private void button2_Click(object sender, EventArgs e)
{
backgroundWorker1.CancelAsync();
}
}
}
除了上述异步模式,传统方法是使用委托的BeginInvoke,EndInvoke方法;
如果委托对象在调用列表中只有一个方法(引用方法),它就可以异步执行这个方法。委托类有BeginInvoke,EndInvoke方法,可以如下方式使用:
使用这一过程有三种标准模式。区别在于,原始线程如何知道发起的线程已经完成。
原始线程发起异步方法并做了一些其他处理后,原始线程中断并等待异步方法完成后再继续。
原始线程定期检查发起的异步方法线程是否完成,如果没有则继续做其他事情。
原始线程一直执行,无需等待,当发起的线程中引用方法完成后,发起的线程就调用回调方法,由回调方法在调用EndInvoke之前处理异步方法的结果。
如上所述,委托对象调用列表只有一个方法时,可通过BeginInvoke方法异步执行。该方法接受委托方法的参数列表外,还接受其它额外参数。返回值为一个IAsyncResult类型的值,该值包含有关新线程的信息。
EndInvoke方法用来获取异步调用返回的值,并释放线程资源。
该方法接收一个IAsyncResult参数引用,即BeginInvoke方法的返回值,通过该参数找到关联线程。
如果线程池中线程已退出,则该方法清理线程状态并释放资源;如果线程在继续,则停止线程并等待,直到资源清理完毕并返回值。
每一个BeginInvoke确保有一个EndInvoke调用;
4.2.2 wait until done
//代码片段
private delegate string GetContent(string uri);
GetContent content;
IAsyncResult invokeResult;
private string GetWebContent(string uri)
{
WebClient client = new WebClient();
return client.DownloadString(new Uri(uri));
}
invokeResult =content.BeginInvoke("http://www.csdn.net",null,null);//具有引用方法的content委托对象
string webstr = content.EndInvoke(invokeResult);
this.label1.Text = "http://www.csdn.net contents length:"+webstr.Length.ToString();
即调用线程通过委托对象的BeginInvoke启动线程后,再接着通过EndInvoke结束线程,获取返回值。
4.2.3 polling
不停的探测BeginInvoke方法返回值的IsCompleted属性。
invokeResult = content.BeginInvoke("http://www.csdn.net", null, null);
while (!invokeResult.IsCompleted)
{
//do something!
}
string webstr = content.EndInvoke(invokeResult);
this.label1.Text = "http://www.csdn.net contents length:" + webstr.Length.ToString();
4.2.2 callback
初始线程通过BeginInvoke启动异步方法后,就自己管自己了。当异步方法完成后,系统调用一个用户自定义的方法处理结果,并且调用EndInvoke方法。这个自定义方法称为回调方法或回调。
BeginInvoke方法最后两个额外参数由回调方法使用,第一个参数AsyncCallback是回调参数的名称,第二个参数是要传入回调方法的对象引用。
有多种方法为BeginInvoke提供AsyncCallback参数。一种是直接提供一个AsyncCallback委托给BeginInvoke,另一种是只提供名称,由系统自动创建AsyncCallback委托。
问题来了,只提供名称,自动创建回调委托,回调委托的具体工作怎么自定义?
在应用程序中实现多线程的首选方式是使用 BackgroundWorker 组件。 BackgroundWorker 组件使用事件驱动模型实现多线程。 后台线程运行 DoWork 事件处理程序,而创建控件的线程运行 ProgressChanged 和 RunWorkerCompleted 事件处理程序。 可以从 ProgressChanged 和 RunWorkerCompleted 事件处理程序调用控件。
当实现的功能比较小时,可以用async/wait特性实现。传统方法比较繁琐,且易于出错。