.NET组件程序设计 第7章 异步调用

异步调用模式:

对象在后台执行方法调用后,控制权立即返回客户端(调用期间客户端不阻塞),随后以某种方式通知客户端该方法执行完毕。

 

委托,你懂的:

当使用一委托调用方法时,委托会阻塞调用者一直到所有目标方法都返回。

委托是能够用于异步调用目标的方法。

委托实际上是编译成类

//声明一个委托
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对象被垃圾回收才关闭。这样就会导致资源浪费。应显示关闭该句柄。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在ASP.NET Web项目中,可以使用Ajax进行异步调用方法。下面是一个C#的代码示例: 1. 首先,在你的Web项目中添加一个Web服务(.asmx文件)或者Web API控制器(.cs文件),定义一个需要被异步调用的方法。 ```csharp // Web服务(.asmx文件)示例 [WebMethod] public string GetServerTime() { return DateTime.Now.ToString(); } // Web API控制器(.cs文件)示例 [HttpGet] public string GetServerTime() { return DateTime.Now.ToString(); } ``` 2. 在前端页面中引入jQuery库和JavaScript代码,使用Ajax进行异步调用。 ```html <!-- 引入jQuery库 --> <script src="https://code.jquery.com/jquery-3.6.0.min.js"></script> <!-- JavaScript代码 --> <script> $(document).ready(function () { // 使用Ajax异步调用方法 $.ajax({ type: "GET", // 请求类型(GET、POST等) url: "YourWebService.asmx/GetServerTime", // Web服务的url或Web API控制器的路由 contentType: "application/json; charset=utf-8", dataType: "json", success: function (response) { // 异步调用成功后的处理 var serverTime = response.d; // 获取返回的数据 console.log(serverTime); }, error: function (xhr, status, error) { // 异步调用失败后的处理 console.log(error); } }); }); </script> ``` 请注意,上述示例中的"YourWebService.asmx/GetServerTime"或者"YourApiController/GetServerTime"需要根据你的实际情况进行替换。另外,如果你的Ajax调用返回的是JSON格式的数据,可以在success回调函数中进行进一步的处理,并根据需要更新页面内容。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值