.NET异步编程模式(二)

打印 上一主题 下一主题

主题 873|帖子 873|积分 2619

在 C#1 的时候就包含了APM,在 APM 模型中,异步操作通过 IAsyncResult 接口实现,包括两个方法 BeginOperationName 和 EndOperationName ,分别表示开始和结束异步操作。
Demo

我们先来看一个同步示例。新建WPF程序,在界面上放一个按钮。点击按钮访问外网,会有一定时间的阻塞。
  1. private void SyncBtn_Click(object sender, RoutedEventArgs e)
  2. {
  3.     // 记录时间
  4.     Debug.WriteLine(DateTime.Now.TimeOfDay.ToString() +
  5.                     ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
  6.     // 访问外网网站网站
  7.     var req = WebRequest.Create("https://docs.newrelic.com/docs/apm/agents/net-agent/getting-started/net-agent-compatibility-requirements-net-framework/");
  8.     req.GetResponse();
  9.     // 记录时间
  10.     Debug.WriteLine(DateTime.Now.TimeOfDay.ToString() +
  11.                     ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
  12. }
复制代码
当我们点击按钮后,因为web请求是同步的,会阻塞UI线程一定时间。从输出日志上看阻塞时间是 1 秒钟左右,此时界面呈卡死状态。

日志输出如下:
  1. 13:16:09.5031834,ThreadID = 1
  2. 13:16:10.5220362,ThreadID = 1
复制代码
从运行效果和日志,我们可以看出:

  • WebRequest方法调用前后都是在同一个线程上执行-UI线程
  • WebReqeust方法阻塞了UI线程,导致“假死”现象
WebRequest也提供了异步方法,BeginGetResponse,EndGetResponse。我们修改一下代码,新增一个按钮。
  1. private void APM_Btn_Click(object sender, RoutedEventArgs e)
  2. {
  3.     // 记录时间
  4.     Debug.WriteLine("1-" + DateTime.Now.TimeOfDay.ToString() +
  5.                     ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
  6.     // 访问外网网站网站
  7.     var req = WebRequest.Create("https://docs.newrelic.com/docs/apm/agents/net-agent/getting-started/net-agent-compatibility-requirements-net-framework/");
  8.     req.BeginGetResponse(new AsyncCallback(t => { WebRequestCallback(t,req); }), null);
  9.     // 记录时间
  10.     Debug.WriteLine("3-" + DateTime.Now.TimeOfDay.ToString() +
  11.                     ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
  12. }
  13. /// <summary>
  14. /// 异步回调
  15. /// </summary>
  16. /// <param name="result"></param>
  17. private void WebRequestCallback(IAsyncResult result, WebRequest request)
  18. {
  19.     var response = request.EndGetResponse(result);
  20.     // 获取返回数据流
  21.     var stream = response.GetResponseStream();
  22.     using(StreamReader reader = new StreamReader(stream))
  23.     {
  24.         StringBuilder sb = new StringBuilder();
  25.         while(!reader.EndOfStream)
  26.         {
  27.             var content = reader.ReadLine();
  28.             sb.Append(content);
  29.         }
  30.         // 记录时间
  31.         Debug.WriteLine("2-" + DateTime.Now.TimeOfDay.ToString() +
  32.                         ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
  33.     }
  34. }
复制代码
运行效果如下:

日志输出如下:
  1. 1-13:10:01.7734197,ThreadID = 1
  2. 3-13:10:01.8826176,ThreadID = 1
  3. 2-13:10:03.2614022,ThreadID = 14
复制代码
从运行效果和日志,我们可以看出:

  • 异步方法不会阻塞调用方法,调用后立刻返回
  • 异步方法会在另外一个线程上执行
IAsyncResult

BeginOperationName 方法会返回一个实现了 IAsyncResult 接口的对象。该对象存储了关于异步操作的信息。

转到定义,我们可以看到接口中都包含哪些内容:

自定义异步方法

实现该接口,定义自己的异步方法。
  1. public class MyWebRequestResult : IAsyncResult
  2. {
  3.     /// <summary>
  4.     /// 用户定义属性,可以存放数据
  5.     /// </summary>
  6.     public object? AsyncState => throw new NotImplementedException();
  7.     /// <summary>
  8.     /// 获取用于等待异步操作完成的 WaitHandle
  9.     /// </summary>
  10.     public WaitHandle AsyncWaitHandle => throw new NotImplementedException();
  11.     /// <summary>
  12.     /// 表示异步操作是否是同步完成
  13.     /// </summary>
  14.     public bool CompletedSynchronously => throw new NotImplementedException();
  15.     /// <summary>
  16.     /// 表示异步操作是否完成
  17.     /// </summary>
  18.     public bool IsCompleted => throw new NotImplementedException();
  19. }
复制代码
我们需要新建一个回调函数:
  1. public class MyWebRequestResult : IAsyncResult
  2. {
  3.     /// <summary>
  4.     /// 异步回调函数
  5.     /// </summary>
  6.     private AsyncCallback _callback;
  7.     public string Result { get; private set; }
  8.     // 构造函数
  9.     public MyWebRequest(AsyncCallback asyncCallback, object state)
  10.     {
  11.         _callback = asyncCallback;
  12.     }
  13.     // 设置结果
  14.     public void SetComplete()
  15.     {
  16.         AsyncState = result;
  17.         Result = result;
  18.         if(null != _callback)
  19.         {
  20.             _callback(this);
  21.         }
  22.     }
  23.     // ...
  24. }
复制代码
在次之后就可以自定义 APM 异步模型了:
  1. public IAsyncResult BeginMyWebRequest(AsyncCallback callback)
  2. {
  3.     // 1. 先给 IAsyncResult 进行赋值
  4.     var myResult = new MyWebRequestResult(callback, null);
  5.     var request = WebRequest.Create("https://docs.newrelic.com/docs/apm/agents/net-agent/getting-started/net-agent-compatibility-requirements-net-framework/");
  6.     // 2. 新建线程,执行耗时任务
  7.     new Thread(() => {
  8.         using (StreamReader sr = new StreamReader(request.GetResponse().GetResponseStream()))
  9.         {
  10.             var str = sr.ReadToEnd();
  11.             // 3. 耗时任务结束后 调用回调函数 & 保存结果
  12.             myResult.SetComplete(str);
  13.         }
  14.     }).Start();
  15.     return myResult;
  16. }
  17. public string EndMyWebRequest(IAsyncResult asyncResult)
  18. {
  19.     MyWebRequestResult myResult = asyncResult as MyWebRequestResult;
  20.     return myResult.Result;
  21. }
复制代码
新增一个按钮,进行调用:
  1. private void MyAPM_Btn_Click(object sender, RoutedEventArgs e)
  2. {
  3.     // 记录时间
  4.     Debug.WriteLine("1-" + DateTime.Now.TimeOfDay.ToString() +
  5.                     ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
  6.     // 调用 Begin 方法
  7.     BeginMyWebRequest(new AsyncCallback(MyAPM_Callback));
  8.     // 记录时间
  9.     Debug.WriteLine("3-" + DateTime.Now.TimeOfDay.ToString() +
  10.                     ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
  11. }
  12. private void MyAPM_Callback(IAsyncResult result)
  13. {
  14.     // 从这里可以获得 异步操作的结果
  15.     var myResult = result as MyWebRequestResult;
  16.     var msg = EndMyWebRequest(myResult);
  17.     // 记录时间
  18.     Debug.WriteLine("2-" + DateTime.Now.TimeOfDay.ToString() +
  19.                     ",ThreadID = " + Thread.CurrentThread.ManagedThreadId);
  20. }
复制代码
运行效果如下:

日志输出如下:
  1. 1-14:48:42.7278184,ThreadID = 1
  2. 3-14:48:42.7311174,ThreadID = 1
  3. 2-14:48:45.1049069,ThreadID = 6
复制代码
结合效果和日志,我们可以得出如下结论:

  • 自定义的异步方法没有导致 UI 卡顿
  • APM就是把耗时的任务交给新线程去做,然后利用委托进行回调
普通方法的异步

如果是普通方法,也可以通过 委托异步(BeginInvoke, EndInvoke):
  1. public void MyAction()
  2. {
  3.     var func = new Func<string, string>(t => {
  4.         Thread.Sleep(2000);
  5.         return t;
  6.     });
  7.     func.BeginInvoke("inputStr", t => {
  8.         string result = func.EndInvoke(t);
  9.     },null);
  10. }
复制代码
总结


  • APM 模型是基于IAsyncResult来实现异步操作的
  • 异步操作开始时,把委托传递给 IAsyncResult
  • 在新线程上执行耗时操作
  • 耗时操作结束后,修改 IAsyncResult 里的结果数据,并调用 IAsyncResult 里的委托回调
  • 在回调里获取 异步操作 的结果

免责声明:如果侵犯了您的权益,请联系站长,我们会及时删除侵权内容,谢谢合作!

本帖子中包含更多资源

您需要 登录 才可以下载或查看,没有账号?立即注册

x
回复

使用道具 举报

0 个回复

倒序浏览

快速回复

您需要登录后才可以回帖 登录 or 立即注册

本版积分规则

半亩花草

金牌会员
这个人很懒什么都没写!
快速回复 返回顶部 返回列表