c#已有10多年历史,单从微软2年一版的更新进度来看活力异常旺盛,c#中的异步编程也经历了多个版本的演化,从今天起着手写一个系列博文,记录一下c#中的异步编程的发展历程。广告一下:喜欢我文章的朋友,请点下面的“关注我”。谢谢
我是2004年接触并使用c#的,那时c#版本为1.1,所以我们就从就那个时候谈起。那时在大学里自己看书写程序,所写的程序大都是同步程序,最多启动个线程........其实在c#1.1的时代已有完整的异步编程解决方案,那就是apm(异步编程模型)。如果还有不了解“同步程序、异步程序”的请自行百度哦。
apm异步编程模型最具代表性的特点是:一个异步功能由以begin开头、end开头的两个方法组成。begin开头的方法表示启动异步功能的执行,end开头的方法表示等待异步功能执行结束并返回执行结果。下面是一个模拟的实现方式(后面将编写标准的apm模型异步实现):
public class worker
{
public int a { get; set; }
public int b { get; set; }
private int r { get; set; }
manualresetevent et;
public void beginwork(action action)
{
et = new manualresetevent(false);
new thread(() =>
{
r = a + b;
thread.sleep(1000);
et.set();
if(null != action)
{
action();
}
}).start();
} public int endwork()
{ if(null == et)
{ t
hrow new exception("调用endwork前,需要先调用beginwork");
}
else
{
et.waitone();
return r;
}
}
}
static void main(string[] args)
{
worker w = new worker();
w.beginwork(()=> {
console.writeline("thread id:{0},count:{1}", thread.currentthread.managedthreadid,
w.endwork());
});
console.writeline("thread id:{0}", thread.currentthread.managedthreadid);
console.readline();
}
在上面的模拟apm模型中我们使用了 thread、manualresetevent,如果你对多线程和manualresetevent不熟悉c#中使用异步编程不可避免的会涉及到多线程的知识,虽然微软在framework中做了很多封装,但朋友们应该掌握其本质。
上面模拟的apm异步模型之所以简单,是因为c#发展过程中引入了很多优秀的语法规则。上例我们较多的使用了lambda表达式,如果你不熟悉 匿名委托与lambda表达式可看我之前的bolg《匿名委托与lambda表达式》。上面做了如此多的广告,下面我们来看一下标准的apm模型如何实现异步编程。
iasyncresult接口
iasyncresult接口定义了异步功能的状态,该接口具体属性及含义如下:
// 表示异步操作的状态。
[comvisible(true)] public interface iasyncresult
{ //
// 摘要: // 获取一个值,该值指示异步操作是否已完成。 //
// 返回结果: // 如果操作已完成,则为 true;否则为 false。
bool iscompleted { get; } //
// 摘要: // 获取用于等待异步操作完成的 system.threading.waithandle。 //
// 返回结果: // 用于等待异步操作完成的 system.threading.waithandle。
waithandle asyncwaithandle { get; } //
// 摘要: // 获取一个用户定义的对象,该对象限定或包含有关异步操作的信息。 //
// 返回结果: // 一个用户定义的对象,限定或包含有关异步操作的信息。
object asyncstate { get; } //
// 摘要: // 获取一个值,该值指示异步操作是否同步完成。 //
// 返回结果: // 如果异步操作同步完成,则为 true;否则为 false。
bool completedsynchronously { get; }
}
注意:模型示例1中的 manualresetevent 继承自 waithandle
apm传说实现方式
在了解了iasyncresult接口后,我们来通过实现 iasyncresult 接口的方式完成对模拟示例的改写工作,代码如下:
public class newworker
{ public class workerasyncresult : iasyncresult
{
asynccallback callback;
public workerasyncresult(int a,int b, asynccallback callback, object asyncstate) {
a = a;
b = b;
state = asyncstate;
this.callback = callback;
new thread(count).start(this);
}
public int a { get; set; }
public int b { get; set; }
public int r { get; private set; }
private object state;
public object asyncstate
{
get
{
return state;
}
}
private manualresetevent waithandle;
public waithandle asyncwaithandle
{
get
{
if (null == waithandle)
{
waithandle = new manualresetevent(false);
}
return waithandle;
}
} private bool completedsynchronously;
public bool completedsynchronously
{ get
{
return completedsynchronously;
}
}
private bool iscompleted;
public bool iscompleted
{
get
{
return iscompleted;
}
}
private static void count(object state)
{
var result = state as workerasyncresult;
result.r = result.a + result.b;
thread.sleep(1000);
result.completedsynchronously = false;
result.iscompleted = true;
((manualresetevent)result.asyncwaithandle).set();
if (result.callback != null)
{
result.callback(result);
}
}
}
public int num1 { get; set; }
public int num2 { get; set; }
public iasyncresult beginwork(asynccallback usercallback, object asyncstate)
{
iasyncresult result = new workerasyncresult(num1,num2,usercallback, asyncstate);
return result;
} public int endwork(iasyncresult result)
{
workerasyncresult r = result as workerasyncresult;
r.asyncwaithandle.waitone(); return r.r;
}
}
示例代码分析:
上面代码中newworker的内部类 workerasyncresult 是关键点,它实现了 iasyncresult 接口并由它来负责开启新线程完成计算工作。
在workerasyncresult中增加了 a、b两个私有属性来存储用于计算的数值,一个对外可读不可写的属性r,用于存储workerasyncresult内部运算的结果。asyncwaithandle属性由 manualresetevent 来充当,并在首次访问时创建manualresetevent(但不释放)。其他接口属性正常实现,没有什么可说。
workerasyncresult 中新增 static count 方法,参数 state 为调用count方法的当前workerasyncresult对象。count 方法在 workerasyncresult 对象的新启线程中运行,因此thread.sleep(1000)将阻塞新线程1秒中。然后设置当前workerasyncresult对象是否同步完成为false,异步完成状态为true,释放manualresetevent通知以便等待线程获取通知进入执行状态,判断是否有异步执行结束回调委托,存在则回调之。
newworker 非常简单,num1、num2两个属性为要计算的数值。beginwork 创建workerasyncresult对象、并将要计算的两个数值num1、num2、usercallback回调委托、object 类型的 asyncstate 传入要创建的workerasyncresult对象。经过此步操作,workerasyncresult对象获取了运算所需的所有数据、运算完成后的回调,并马上启动新线程进行运算(执行workerasyncresult.count方法)。
因为workerasyncresult.count执行在新线程中,在该线程外部无法准确获知新线程的状态。为了满足外部线程与新线程同步的需求,在newworker中增加endwork方法,参数类型为iasyncresult。要调用endwork方法应传入beginwork 获取的workerasyncresult对象,endwork方法获取workerasyncresult对象后,调用workerasyncresult.asyncwaithandle.waitone()方法,等待获取manualresetevent通知,在获取到通知时运算线程已运算结束(线程并未结束),下一步获取运算结果r并返回。
接下来是newworker调用程序,如下:
static void main(string[] args)
{
newworker w2 = new newworker();
w2.num1 = 10;
w2.num2 = 12;
iasyncresult r = null;
r = w2.beginwork((obj) => {
console.writeline("thread id:{0},count:{1}",thread.currentthread.managedthreadid,
w2.endwork(r));
}, null);
console.writeline("thread id:{0}", thread.currentthread.managedthreadid);
console.readline();
}
下图我简单画的程序调用过程,有助于各位朋友理解:
标准的apm模型异步编程,对应开发人员来说过于复杂。因此通过实现 iasyncresult 接口进行异步编程,就是传说中的中看不中用(罪过罪过.....)。
delegate异步编程(apm 标准实现)
c#中委托天生支持异步调用(apm模型),任何委托对象后"."就会发现begininvoke、endinvoke、invoke三个方法。begininvoke为异步方式调用委托、endinvoke等待委托的异步调用结束、invoke同步方式调用委托。因此上面的标准apm实例,可借助 delegate 进行如下简化。
上面newworker使用委托方式改写如下:
public class newworker2
{
func<int, int, int> action; public newworker2()
{
action = new func<int, int, int>(work);
} public iasyncresult beginwork(asynccallback callback, object state)
{ dynamic obj = state; return action.begininvoke(obj.a, obj.b, callback, this);
} public int endwork(iasyncresult asyncresult)
{ try
{ return action.endinvoke(asyncresult);
} catch (exception ex)
{ throw ex;
}
} private int work(int a, int b)
{
thread.sleep(1000); return a + b;
}
}
调用程序:
static void main(string[] args)
{
newworker2 w2 = new newworker2();
iasyncresult r = null;
r = w2.beginwork((obj) =>
{
console.writeline("thread id:{0},count:{1}", thread.currentthread.managedthreadid,
w2.endwork(r));
}, new { a = 10, b = 11 });
console.writeline("thread id:{0}", thread.currentthread.managedthreadid);
console.readline();
}
上面的使用委托进行apm异步编程,比实现 iasyncresult 接口的方式精简太多、更易理解使用。因此这里建议朋友们 delegate 异步调用模型应该掌握起来,而通过实现 iasyncresult 接口的传说方式看你的喜好吧。
以上就是c#异步之apm模式异步程序开发的示例分享的详细内容。