异步调用模式:
对象在后台执行方法调用后,控制权立即返回客户端(调用期间客户端不阻塞),随后以某种方式通知客户端该方法执行完毕。
委托,你懂的:
当使用一委托调用方法时,委托会阻塞调用者一直到所有目标方法都返回。
委托是能够用于异步调用目标的方法。
委托实际上是编译成类。
//声明一个委托
public delegate void MyDelegate();
//委托实际编译为类
public sealed class MyDelegate : MulticastDelegate
{
public MyDelegate(object target, int methodPtr)
{...}
//方法调用会阻塞调用者线程
public virtual int Invoke(...)
{...}
//发起一个异步方法调用,仅阻塞片刻:调用请求放入队列,该请求由线程池分配一线程执行,控制权返回客户端
public virtual IAsyncResult BeginInvoke(...)
{...}
//管理异步方法完成,包括输出参数和返回值
public virtual int EndInvoke(IAsyncResult result)
{...}
}
BeginInvoke()和EndInvoke():
//定义委托
public delegate string MyDelegate(int number1, out int number2, ref int number3, object obj);
//编译后生成BeginInvoke()
//接受委托定义所有原始签名参数(包括值类型,引用类型)
//callback参数:一委托对象,代表对一方法引用,用于接受方法完成通知,可选
//asyncState参数:一通用对象,表示该对象状态信息,可选
public virtual IAsyncResult BeginInvoke(int number1, out int number2, ref int number3, object obj,
AsyncCallback callback, object asyncState);
//编译后生成EndInvoke()
//原始方法返回值,显示输出参数(ref,out)都是其方法一部分
/* 主要用于获取所有输出参数方法返回值,该方法阻塞调用者直到方法返回 */
public virtual string EndInvoke(out int number2, ref int number3, IAsyncResult asyncResult);
IAsyncResult接口:
每个BeginInvoke()方法都返回一个实现IAsyncResult接口的对象。该对象唯一标识了通过BeginInvoke()调用的方法,用于传递给EndInvoke()。
//IAsyncResult接口
public interface IAsyncResult
{
object AsyncState{get;}
WaitHandle AsyncWaitHandle{get;}
bool CompletedSynchronously{get;}
bool IsCompleted{get;}
}
注意:
1.EndInvoke()每次异步调用只能用一次。
2.使用异步调用,委托对象列表只允许有一个目标方法。
3.IAsyncResult对象传递给不同委托会导致异常。
AsyncResult类:
IAsyncResult对象本身能标记创建自身的委托,BeginInvoke()返回的IAsyncResult引用,实际为一AsyncResult类实例。
using System.Runtime.Remoting.Messaging;
using System.Diagnostics;
public partial class Default4 : System.Web.UI.Page
{
private IAsyncResult m_AsyncResult;
//定义一个委托类
public delegate string AddDelegate(string str1, string str2);
protected void Page_Load(object sender, EventArgs e)
{
//实例化委托类指定要调用的方法
AddDelegate addDelegate = Add;
//开始异步调用该方法
m_AsyncResult = addDelegate.BeginInvoke("Hello ", "World!", null, null);
string result = GetResult();
//string result = addDelegate.EndInvoke(m_AsyncResult);
Response.Write(result);
}
//委托调用的方法
private string Add(string str1, string str2)
{
return str1 + str2;
}
//通过通过AsyncResult类获取原始委托,完成异步调用
private string GetResult()
{
//通过AsyncResult类获得原始委托
AsyncResult asyncResult = (AsyncResult)m_AsyncResult;
AddDelegate addDelegate = (AddDelegate)asyncResult.AsyncDelegate;
//验证EndInvoke方法是否已经调用
Debug.Assert(asyncResult.EndInvokeCalled == false);
return addDelegate.EndInvoke(m_AsyncResult);
}
}
轮循或等待完成异步编程模式:
//此处可能阻塞,直到轮循方法完成
while (m_AsyncResult.IsCompleted == false)
{
//设置阻塞时间,超时后无论方法是否执行完毕都将返回
m_AsyncResult.AsyncWaitHandle.WaitOne(1000, false);
}
//此处不阻塞
string result = addDelegate.EndInvoke(m_AsyncResult);
/********** 等待多个方法完成 ***********/
//实例化委托类指定要调用的方法
AddDelegate addDelegate1 = Add;
AddDelegate addDelegate2 = Add;
//开始异步调用该方法
IAsyncResult asyncResult1 = addDelegate1.BeginInvoke("Hello ", "World!", null, null);
IAsyncResult asyncResult2 = addDelegate2.BeginInvoke("Love ", "You!", null, null);
//WaitAll()等待多个方法全部完成
WaitHandle[] handlers = { asyncResult1.AsyncWaitHandle, asyncResult2.AsyncWaitHandle };
WaitHandle.WaitAll(handlers);
//等待其中任一方法完成
//WaitHandle.WaitAny(handlers);
//此处不会阻塞
Response.Write(addDelegate1.EndInvoke(asyncResult1));
Response.Write(addDelegate2.EndInvoke(asyncResult2));
完成回调异步编程模式(首选):
AddDelegate addDelegate = Add;
addDelegate.BeginInvoke("Hello ", "World!", OnMethodCompletion, null);
//此处完成回调不会阻塞调用线程
//Thread.Sleep(4000);
Response.Write(str);
//回调方法:无返回值,含有一个IAsyncResult参数的方法
//回调方法在线程池中某一线程执行,需考虑线程安全
private void OnMethodCompletion(IAsyncResult asyncResult)
{
//通过IAsyncResult对象,获取唯一原始委托对象
AsyncResult result = (AsyncResult)asyncResult;
AddDelegate addDelegate = (AddDelegate)result.AsyncDelegate;
str = addDelegate.EndInvoke(asyncResult);
//清理WaitHandle对象占用的资源
asyncResult.AsyncWaitHandle.Close();
}
传递状态信息:
BeginInvoke()最后一个参数,asyncState对象,为一状态对象,通用容器。指示自定义状态信息。
AddDelegate addDelegate = Add;
//使用状态对象传递额外辅助信息
int asyncState = 4;
addDelegate.BeginInvoke("Hello ", "World!", OnMethodCompletion, asyncState);
//回调方法
private void OnMethodCompletion(IAsyncResult asyncResult)
{
//通过IAsyncResult对象获取额外辅助信息
int asyncResult = (int)asyncResult.AsyncState;
}
异步错误处理:
当异步方法抛出一个异常,.NET捕获这个异常,等到客户端调用EndInvoke()时再抛出这个异常。
异步事件:
发布者触发一个事件,会阻塞,直到所有订阅者完成事件处理。如果一个订阅者事件处理需要花时间,这样会阻止其他订阅者处理事件。对于无纪订阅者,解决方案就是异步触发事件。
//定义委托
public delegate void GenericEventHandler<T, U>(T t, U u);
private static string str;
private static string str2;
protected void Page_Load(object sender, EventArgs e)
{
Subscriber s = new Subscriber();
Publisher p = new Publisher();
//添加订阅
p.AddHandler += s.OnAdd;
p.AddHandler += s.OnAdd2;
//异步触发事件
p.FireEventAsync();
Thread.Sleep(1000);
//输出:str2
Response.Write(str + "<br/>" + str2);
}
//发布事件对象
public class Publisher
{
public event GenericEventHandler<Publisher, EventArgs> AddHandler;
//异步触发事件(避免无纪订阅者)
//当发布者触发一个事件,会阻塞直到所有订阅者都完成事件处理
//以异步方式触发事件中每个委托方法,控制权立刻返回发布者,无阻塞
public void FireEventAsync()
{
EventsHelper.FireAsync<Publisher, EventArgs>(AddHandler, this, EventArgs.Empty);
//AddHandler(this, EventArgs.Empty);
}
}
//订阅事件对象
public class Subscriber
{
//无纪订阅者,需等待较长时间完成操作
public void OnAdd(object sender, EventArgs args)
{
Thread.Sleep(100000000);
str = "str1";
}
public void OnAdd2(object sender, EventArgs args)
{
str2 = "str2";
}
}
//防御性类型安全异步触发事件
public static class EventsHelper
{
//定义委托
private delegate void AsyncFireDelegate(Delegate del, object[] args);
//防御性类型安全异步触发事件
public static void UnsafeFireAsync(Delegate del, params object[] args)
{
if (del == null)
{
return;
}
Delegate[] delegates = del.GetInvocationList();
AsyncFireDelegate asyncFire = Invoke;
//匿名方法,清理WaitHandle句柄占用资源
AsyncCallback cleanup = delegate(IAsyncResult asyncResult)
{
asyncResult.AsyncWaitHandle.Close();
};
foreach (Delegate sink in delegates)
{
//异步执行事件,因为事件无返回值,只需BeginInvoke()执行方法即可
asyncFire.BeginInvoke(sink, args, cleanup, null);
}
}
//执行委托
private static void Invoke(Delegate del, object[] args)
{
del.DynamicInvoke(args);
}
public static void FireAsync<T, U>(GenericEventHandler<T, U> del, T t, U u)
{
UnsafeFireAsync(del, t, u);
}
}
异步调用缺陷:
1.线程并发和同步:异步方法是被在.NET线程池中线程调用,有线程并发,状态丢失等潜在问题。
2.线程池消耗:线程池非无限大,操作系统携带太多线程的线程池是一个累赘。避免过度使用异步调用或异步调用占长时间阻塞操作。
3.对引用类型过早访问:如异步方法签名包含引用对象,异步方法要求改变这些对象值时,保证在EndInvoke()方法调用后,才可取到正确的值。
EndInvoke后的清理:
无论何时调用BeginInvoke(),委托都会创建一新AsyncResult对象并返回。该对象存在一指向WaitHandle对象的引用。调用EndInvoke()并不关闭该句柄,除非等到AsyncResult对象被垃圾回收才关闭。这样就会导致资源浪费。应显示关闭该句柄。