学习笔记 --- 编码过程中常见的三种异步方式
实际的编码过程中, 凡是涉及到网络通信的代码, 异步都是决不可缺少的. 那么什么是异步呢?
异步就是子线程, 异步通过开辟子线程来实现, 所以一提到异步就应该想到子线程. 即使不涉及网络通信, 异步也是一种常用的编码方式, 如: 在Winfor程序中, 某个耗时较长方法我们需要执行10000次, 如果由主线程去循环10000次, 那么客户的CPU占用率将居高不下, 客户机器将慢如蜗牛, 甚至整个程序都会出现假死或崩溃的状态. 作为一名有追求的程序员, 这种情况是绝对不允许的. 其实不光是在Winform程序, 即使是在基于B/S的Web程序中, 异步通信也是很常见的, 比如: Ajax技术. 在这里, 不讨论具体的项目环境, 单说3种常见的异步编程(Asynchronous Programming)方式 和 简要的提下B/S中的异步技术 --- Ajax(参见博客中的<<3种Ajax的实现>>)
关于示例的说明: 我们举个很简单的例子: 计算1+2+3+...+99+100, 采用最笨的循环方法且每次循环Sleep 100毫秒. 由于窗体程序容易看出效果, 这里在主窗体(MainForm)中放置3个按钮, 代表3中不同的异步方式. 当点击按钮时通过this.Invoke()方法创建子窗体(SubForm), 在第一种异步方式中, 我们是在子线程中创建子窗体的, 如果只是简单的使用new创建子窗体, 则子窗体会随着子线程的结束而消亡, 结果就看不到了. 使用this.Invoke()方法可以在子线程中, 由主线程来创建子窗体, 这样子线程执行完后并不会销毁主线程创建的窗体. 同时为了将结果显示在子窗体的textbox中, 需要在子窗体的设计代码中, 将textbox的修饰符设置为public的. 注意: 第二种异步方式中, 使用new创建了子窗体, 由于是在主线程执行的new, 所以子窗体不会销毁, 注意体会this.Invoke()的用法.
第一种异步: 使用System.Threading.Thread类 + Start()方法.
这种方法需要先New一个Thread类的对象, 在Thread类的构造函数的参数列表中需要再new 一个TheadStart委托类的委托对象(还记得吗? 委托是用来传递方法的), 该委托对象上绑定要执行的处理方法. 最后, 只要在主线程中使用Thread类的对象调用Start()方法即可.
第二种异步: 使用委托 + AsyncCallback回调函数.
这种方式首先要定义一个委托类型, 之后要在主线程中创建委托对象, 该委托对象绑定你要执行的方法. 在委托对象上使用BeginInvoke方法开始异步, BeginInvoke方法的参数列表依次是委托对象绑定的方法的参数、new一个AsyncCallback的委托对象, 该对象绑定处理结果的方法(因为一开始就创建了委托对象, 为避免混淆我个人喜欢将这个用于回调的委托称为回调函数)、委托对象的引用(一开始创建的委托对象).
其实BeginInvoke方法的最后一个参数就是我们一开始创建的委托对象的引用, 保存该引用的目的是为了能够在子线程中调用一开始创建的委托对象上的EndInvoke方法以便处理结果. 处理结果的子线程方法(或者称为结果处理方法, 也就是用于回调的委托上绑定的方法)的参数是IAsyncResult, 这个接口类型的参数实际上就是BeginInvoke方法中的最后一个参数, 我们在这个结果处理方法中, 可以通过EndInvoke方法获得结果并显示.
第三种方式: 使用Async结尾的方法 + 事件处理器.
以上的两种异步是通用的异步手段, 你可以随时使用他们实现异步操作. 但第3种借助事件处理器来完成异步操作的方式并不是随时都可以使用的, 只有类库提供了Async结尾的方法时, 才可以使用. 我们通过Async结尾的方法来开辟子线程, 当子线程结束时激发一个事件, 我们在这个事件中完成结果处理代码. 当然, 我们也可以在类中自己实现Async结尾的方法, 记得常委同志还跟我讨论过这个问题, 今天就额外写个实现.
第四种方式: Javascript中的异步技术, 也就是传说中Ajax技术.
具体实例和代码可以参考博客中的另外一篇文章<<3中Ajax的实现>>, 这里简单陈述一下. 第一种Ajax技术就是在Javascript中异步获取结果的方式, 它的实现原理其实就是onreadystatechange事件, 通过该事件上绑定的事件处理器来完成异步. 第二种Ajax技术要稍微复杂一下, 它的原理是借助AjaxPro.dll类库, 在本地生成一个Javascript中用的代理类, 你可以用这个代理类来完成存取数据的操作. 第3种Ajax技术, 就是微软在VS2008中提供的Ajax控件, 利用该控件也可以很方便实现Ajax技术, 使用也比较简单跟着说明做即可.
//示例代码:
//mainform.cs
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace AsynchronousProgramming
{
public partial class Mainform : Form
{
public Mainform()
{
InitializeComponent();
Control.CheckForIllegalCrossThreadCalls = false;
}
//#####通过Invoke方法使主线程获得子线程的控制权#######
private delegate SubForm DelNewForm();
private SubForm createSubForm()
{
SubForm sf = new SubForm();
sf.Show();
return sf;
}
//####################################################
private void button1_Click(object sender, EventArgs e)
{
#region 同步调用
//SubForm sf = new SubForm();
//sf.Show();
//sf.txt_show.Text = sf.Add(1,100).ToString(); //这里为简单, 将参数硬编码在这
#endregion
#region Thread类 + Start()方法 实现异步
System.Threading.Thread th = new System.Threading.Thread(new System.Threading.ThreadStart(dealProcess));
th.Start();
#endregion
}
private void dealProcess()
{
//SubForm sf = new SubForm(); //直接在子线程中new子窗体, 线程结束子窗体就会关闭, 无法看到结果
SubForm sf = this.Invoke(new DelNewForm(createSubForm)) as SubForm; //第一种异步按钮弹出的子窗体
sf.Show();
sf.txt_show.Text = sf.Add(1,100).ToString();
}
//####################################################
SubForm subf; //第二种异步弹出的子窗体
delegate int DelAdd(int start, int end);
private void button2_Click(object sender, EventArgs e)
{
#region 委托 + AsyncCallback回调函数 实现异步
//subf = this.Invoke(new DelNewForm(createSubForm)) as SubForm;
subf = new SubForm(); //这里可以直接new SubForm(), 因为第二种异步子窗体不是在子线程中创建的, 不会随着线程结束消亡
subf.Show();
DelAdd da = new DelAdd(subf.Add);
da.BeginInvoke(1,100,new AsyncCallback(dealAdd), da); //硬编码参数
#endregion
}
private void dealAdd(IAsyncResult ia)
{
DelAdd da = ia.AsyncState as DelAdd;
subf.txt_show.Text = da.EndInvoke(ia).ToString();
}
//####################################################
SubForm subfm; //第三种异步弹出的子窗体
private void button3_Click(object sender, EventArgs e)
{
#region 使用Async结尾的方法 + 事件处理器 实现异步
//subfm = this.Invoke(new DelNewForm(createSubForm)) as SubForm;
subfm = new SubForm();
subfm.Show();
subfm.AccumulateAsync(1, 100); //我们在SubForm.cs中自定义的Async异步方法(硬编码参数)
subfm.AccumulateCompleted += new AccumulateCompletedHandler(subfm_AccumulateCompleted); //异步计算完成后触发的AccumulatedCompleted事件
#endregion
}
void subfm_AccumulateCompleted(object sender, AccumulatedCompletedEventArgs e)
{
subfm.txt_show.Text = e.Result.ToString();
}
}
}
//SubForm.cs
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
namespace AsynchronousProgramming
{
public partial class SubForm : Form
{
public SubForm()
{
InitializeComponent();
//this.AccumulateCompleted += new AccumulateCompletedHandler(SubForm_AccumulateCompleted); //第三种异步 --- 注册事件
}
//########################################################
#region 第一种异步(Thread类 + Start()方法) 和 第二种异步(委托 + AsyncCallback回调函数) 调用的方法
public int Add(int start , int end)
{
int sum = 0;
for (int i = start; i <= end; i++)
{
System.Threading.Thread.Sleep(100);
sum += i;
}
return sum;
}
#endregion
//########################################################
#region 第三种异步(Async结尾的方法 + 事件处理器) 调用的方法
//实际计算的方法
public object Accumulate(int start, int end)
{
int result = 0;
for (int i = start; i <= end; i++)
{
System.Threading.Thread.Sleep(100);
result += i;
}
return (object)result;
}
//3. 定义事件, 该事件就是mainform.cs中用来获取结果的事件
public event AccumulateCompletedHandler AccumulateCompleted;
//4. 注册事件, 事件的注册工作通常放在构造函数中, 也可以由事件来注册. 这里实际的事件注册过程在mainform.cs中注册, 事件处理器也放在mainform.cs中
//5. 触发事件由OnAccumulateCompleted执行
public void OnAccumulateCompleted(object obj)
{
if (AccumulateCompleted != null)
{
this.AccumulateCompleted(this, new AccumulatedCompletedEventArgs(new object[] { obj }));
}
}
private delegate object AccumulateOperation(int start, int end);
//对外提供的AddAsync方法
public void AccumulateAsync(int start, int end) //注意: Async结尾的方法将开辟子线程, 所以返回值为void
{
//此时, 应该异步的调用Accumulate方法, 这里用委托+回调方法来模拟
AccumulateOperation ao = new AccumulateOperation(Accumulate);
ao.BeginInvoke(start, end, new AsyncCallback(dealAccumulate), ao);
}
private void dealAccumulate(IAsyncResult ia)
{
//在子线程中调用Add方法
AccumulateOperation ao = ia.AsyncState as AccumulateOperation;
object result = ao.EndInvoke(ia);
if (result != null)
{
this.OnAccumulateCompleted(result); //结果计算出来后, 触发事件, 事件处理方法在mainform.cs中
}
}
#endregion
}
//1. 定义累加结果处理方法(事件处理器)的参数对象, 因为我们从该事件处理器中获得结果信息, 因此参数对象只包含处理结果的信息即可
public class AccumulatedCompletedEventArgs : EventArgs //为了扩大结果的表示范围, 采用object[]做结果信息类型
{
private object[] _result;
public AccumulatedCompletedEventArgs(object[] result)
{
this._result = result;
}
public int Result
{
get { return (int)this._result[0];}
}
}
//2. 定义委托类型, 该委托类型用来封装mainform.cs中处理结果的方法(即: AccumulatedCompleted事件的处理器)
public delegate void AccumulateCompletedHandler(object sender, AccumulatedCompletedEventArgs e);
}