《CLR Via C# 第3版》笔记之(二十一) - 异步编程模型(APM)

本文涉及的产品
应用实时监控服务-用户体验监控,每月100OCU免费额度
应用实时监控服务-应用监控,每月50GB免费额度
简介:

APM是.NET中异步编程模型的缩写(Asynchronous Programing Model)。

通过异步编程,使得我们的程序可以更加高效的利用系统资源。

主要内容:

  • 一个APM的例子
  • GUI中的APM
  • APM的优劣点
  • AMP使用中的注意事项 

1. 一个APM的例子

.Net中的异步模型非常完善,只要看到Begin***者End***方法。基本都是相对***方法的异步调用方式。

(注:***是方法的名称)

所以在.Net中实现一个异步调用是很方便的,下面用个小例子来演示一个异步操作。

首先是同步的方式请求百度搜索10次。(分别搜索1,2,3。。。。10)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public  class  CLRviaCSharp_21
{
     static  void  Main( string [] args)
     {
         // 用百度分别检索1,2,3。。。9,共检索10次
         DateTime start = DateTime.Now;
         string  strReq = "http://www.baidu.com/s?wd={0}" ;
         for  ( int  i = 0; i < 10; i++)
         {
             var  req = WebRequest.Create( string .Format(strReq, i));
             // 注意这里的GetResponse是同步方法
             var  res = req.GetResponse();
 
             Console.WriteLine( "检索 {0} 的结果已经返回!" , i);
 
             res.Close();
         }
 
         Console.WriteLine( "耗用时间:{0}毫秒" , TimeSpan.FromTicks(DateTime.Now.Ticks - start.Ticks).TotalMilliseconds);
         Console.ReadKey( true );
     }
}

结果如下:总共消耗时间33秒多。

image

 

异步的方式如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
public  class  CLRviaCSharp_21
{
     static  DateTime start;
     static  void  Main( string [] args)
     {
         // 用百度分别检索1,2,3。。。9,共检索10次
         start = DateTime.Now;
         string  strReq = "http://www.baidu.com/s?wd={0}" ;
         for  ( int  i = 0; i < 10; i++)
         {
             var  req = WebRequest.Create( string .Format(strReq, i));
             // 注意这里的BeginGetResponse就是异步方法
             var  res = req.BeginGetResponse(ProcessWebResponse, req);
         }
 
         Console.ReadKey( true );
     }
 
     private  static  void  ProcessWebResponse(IAsyncResult result)
     {
         var  req = (WebRequest)result.AsyncState;
         string  strReq = req.RequestUri.AbsoluteUri;
         using  ( var  res = req.EndGetResponse(result))
         {
             Console.Write( "检索 {0} 的结果已经返回!\t" , strReq.Substring(strReq.Length - 1));
             Console.WriteLine( "耗用时间:{0}毫秒" , TimeSpan.FromTicks(DateTime.Now.Ticks - start.Ticks).TotalMilliseconds);
         }
     }
 
}

结果如下:总共消耗的时间应该是下面所有时间中最长的那个,即9.5秒左右。

image

 

2. GUI中的APM

异步编程除了在服务端会大量应用,在有GUI的客户端也应用比较多(为了保证客户端的界面不会假死)。

但是Winform或WPF程序中,改变界面元素状态只有通过UI线程,其他线程如果试图改变UI元素,就会抛出异常(System.InvalidOperationException)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
using  System;
using  System.Windows.Forms;
using  System.Net;
 
public  class  CLRviaCSharp_21
{
     static  void  Main( string [] args)
     {
         MyWindowsForm mf = new  MyWindowsForm();
         mf.ShowDialog();
     }
}
 
internal  class  MyWindowsForm : Form
{
     public  MyWindowsForm()
     {
         Text = "Click in the window to start a web request" ;
         Width = 800;
         Height = 600;
     }
 
     protected  override  void  OnMouseClick(MouseEventArgs e)
     {
         // 开始异步web请求
         Text = "web request initilized" ;
         var  webreq = WebRequest.Create( "http://www.baidu.com" );
         webreq.BeginGetResponse(ProcessWebResponse, webreq);
         base .OnMouseClick(e);
     }
 
     private  void  ProcessWebResponse(IAsyncResult result)
     {
         var  req = (WebRequest)result.AsyncState;
         using  ( var  res = req.EndGetResponse(result))
         {
             // SynchronizationContext.Current.Post(updateUI, res);
             // 这里改变UI元素Form的Text属性,抛出了异常
             Text = "Content length: "  + res.ContentLength;
         }
     }
}

那么其他线程如何改变UI元素呢。为了保证UI始终有响应,必须能够让其他线程也能改变UI元素。

答案就在上面代码中抛异常的上面一句代码。利用SynchronizationContext的Current属性来获取UI线程的同步上下文信息。

然后调用其Post方法,异步的更新UI元素。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
using  System;
using  System.Windows.Forms;
using  System.Net;
using  System.Threading;
 
public  class  CLRviaCSharp_21
{
     static  void  Main( string [] args)
     {
         MyWindowsForm mf = new  MyWindowsForm();
         mf.ShowDialog();
     }
}
 
internal  class  MyWindowsForm : Form
{
     public  MyWindowsForm()
     {
         Text = "Click in the window to start a web request" ;
         Width = 800;
         Height = 600;
     }
 
     protected  override  void  OnMouseClick(MouseEventArgs e)
     {
         // 开始异步web请求
         Text = "web request initilized" ;
         var  webreq = WebRequest.Create( "http://www.baidu.com" );
         webreq.BeginGetResponse(ProcessWebResponse, webreq);
         base .OnMouseClick(e);
     }
 
     private  void  ProcessWebResponse(IAsyncResult result)
     {
         var  req = (WebRequest)result.AsyncState;
         using  ( var  res = req.EndGetResponse(result))
         {
             SynchronizationContext.Current.Post(updateUI, res);
         }
     }
 
     private  void  updateUI( object  state)
     {
         var  res = (WebResponse)state;
         // 这里改变UI元素Form的Text属性, updateUI这个函数是由UI线程执行的
         Text = "Content length: "  + res.ContentLength;
     }
}

这样的实现看着很繁琐,所以《CLR via C#》作者Jeffrey实现了一个小方法(SyncContextCallback)来简化这个步骤。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
using  System;
using  System.Windows.Forms;
using  System.Net;
using  System.Threading;
 
public  class  CLRviaCSharp_21
{
     static  void  Main( string [] args)
     {
         MyWindowsForm mf = new  MyWindowsForm();
         mf.ShowDialog();
     }
}
 
internal  class  MyWindowsForm : Form
{
     public  MyWindowsForm()
     {
         Text = "Click in the window to start a web request" ;
         Width = 800;
         Height = 600;
     }
 
     protected  override  void  OnMouseClick(MouseEventArgs e)
     {
         // 开始异步web请求
         Text = "web request initilized" ;
         var  webreq = WebRequest.Create( "http://www.baidu.com" );
         webreq.BeginGetResponse(SyncContextCallback(ProcessWebResponse), webreq);
         base .OnMouseClick(e);
     }
 
     private  void  ProcessWebResponse(IAsyncResult result)
     {
         var  req = (WebRequest)result.AsyncState;
         using  ( var  res = req.EndGetResponse(result))
         {
             // 这里改变UI元素Form的Text属性,
             // 这次是在UI线程执行的,SyncContextCallback中获取了UI线程的同步上下文,
             // 然后用Post方法调用了ProcessWebResponse函数
             Text = "Content length: "  + res.ContentLength;
         }
     }
 
     // 大牛Jeffrey实现的SyncContextCallback方法
     private  static  AsyncCallback SyncContextCallback(AsyncCallback callback)
     {
         SynchronizationContext sc = SynchronizationContext.Current;
         // 如果没有同步上下文,直接返回传入的东西
         if  (sc == null ) return  callback;
 
         // 返回一个委托,这个委托将委托Post到捕捉到的SC中
         return  asyncResult => sc.Post(result => callback((IAsyncResult)result), asyncResult);
     }
}

 

3. APM的优劣点

上面两个例子,可以看出APM的一些优势,第一个例子说明异步的效率高。第二个GUI的例子说明异步可以增强用户体验。

其实APM的优势总结起来有以下几点:

  1. 资源利用率底(比如I/O并发读写时,线程不必等I/O完了就可以处理其他请求。这样几个线程就可以处理大量请求)
  2. 垃圾回收快(线程不用等待请求的操作完成就回线程池等待了,这时的线程在它们的栈顶,垃圾回收时遍历线程查找根比较快)
  3. 线程少,调试快(调试时,一旦遇到断点就会挂起所有线程。异步可以保证线程尽量少,所以调试时感觉会快)
  4. 使得GUI一直保持响应(上面的GUI例子可以说明)
  5. 并发执行下载,速度快(上面第一个例子可以说明)

当然,任何技术都不是完美的,有其适用的一面,也有其不适用的一面。

了解它的缺点,有时候甚至可以帮助我们更好的使用它。

APM的劣势主要有以下几点:

  1. 必须将代码分成多个回调方法(执行流程不直观)
  2. 避免使用实参和局部变量(实参和局部变量在回调方法中无法访问,记住回调方法在另一个线程中)
  3. 许多C#构造无法使用,比如try/catch/finally,using等(因为没法在一个方法中开始try,再在它的回调方法中完成finally)
  4. 有些功能很难实现,比如多个并发操作协作进行,取消和超时(比如上面例子中的更新GUI元素也很麻烦)

 

4. AMP使用中的注意事项

使用APM时以下几点需要注意:

  1. 不要提早调用End***方法,在回调函数中再调用End***(如果提早调用,异步操作没有完成,End***会一直等待,此时相当于同步调用)
  2. 调用End***且仅调用一次(异步操作初始化时分配的某些资源,必须调用End***才能释放。第二次调用End***时结果不可预测)
  3. 使用相同的对象调用Begin***方法和End***方法
  4. 在Begin***方法和End***方法中使用ref,out,params标记时需要注意(具体实现方法以后有空再补些例子:))
  5. 不能取消异步I/O操作(启动I/O操作后,控制权交给了I/O相关硬件)
  6. Begin***方法返回引用类型,无法返回值类型
  7. 用于打开文件的Win32 API(CreateFile)暂时没有异步的版本
  8. FileStream类在创建时就决定了是异步还是同步的方式(创建式指定异步标记,即使调用Read,也是通过Sleep来模拟同步。同样,指定了同步标记,调用BeginRead也是模拟的异步)
标签:  CLR via C#笔记



本文转自wang_yb博客园博客,原文链接:http://www.cnblogs.com/wang_yb/archive/2011/11/29/2267790.html,如需转载请自行联系原作者

相关实践学习
通过云拨测对指定服务器进行Ping/DNS监测
本实验将通过云拨测对指定服务器进行Ping/DNS监测,评估网站服务质量和用户体验。
目录
相关文章
|
7月前
|
C# Python
C# 笔记1 - 操作目录
C# 笔记1 - 操作目录
56 0
|
7月前
|
C# 开发者
深入理解C#中的`Task<T>`:异步编程的核心
【1月更文挑战第3天】本文旨在探讨C#中`Task<T>`的使用和理解,作为异步编程模式的核心组件。`Task<T>`允许开发者在不阻塞主线程的情况下执行异步操作,并返回一个指定类型`T`的结果。通过定义返回`Task<T>`的异步方法、使用`async`和`await`关键字、处理异常以及获取任务结果,开发者可以编写出高效且响应迅速的应用程序。此外,本文还介绍了如何配置任务以及实现任务的连续性和组合,为掌握C#中的异步编程提供了全面的指导。
|
PyTorch API C#
【Python+C#】手把手搭建基于Hugging Face模型的离线翻译系统,并通过C#代码进行访问
目前翻译都是在线的,要在C#开发的程序上做一个可以实时翻译的功能,好像不是那么好做。而且大多数处于局域网内,所以访问在线的api也显得比较尴尬。于是,就有了以下这篇文章,自己搭建一套简单的离线翻译系统。以下内容采用python提供基础翻译服务+ C#访问服务的功能,欢迎围观。
1098 0
【Python+C#】手把手搭建基于Hugging Face模型的离线翻译系统,并通过C#代码进行访问
|
2月前
|
算法 安全 测试技术
C#——刘铁猛笔记
C#——刘铁猛笔记
48 0
|
3月前
|
C# UED
C#一分钟浅谈:异步编程基础 (async/await)
在现代软件开发中,异步编程对于提升应用性能和响应性至关重要,尤其是在处理网络请求和文件读 异步编程允许程序在等待操作完成时继续执行其他任务,从而提高用户体验、高效利用资源,并增强并发性。在 C# 中,`async` 用于标记可能包含异步操作的方法,而 `await` 则用于等待异步操作完成。 示例代码展示了如何使用 `async` 和 `await` 下载文件而不阻塞调用线程。此外,本文还讨论了常见问题及解决方案,如不在 UI 线程上阻塞、避免同步上下文捕获以及正确处理异常。
52 0
|
4月前
|
API C# 数据库
SemanticKernel/C#:实现接口,接入本地嵌入模型
SemanticKernel/C#:实现接口,接入本地嵌入模型
86 1
|
4月前
|
API C#
SemanticKernel/C#:使用Ollama中的对话模型与嵌入模型用于本地离线场景
SemanticKernel/C#:使用Ollama中的对话模型与嵌入模型用于本地离线场景
104 0
|
6月前
|
关系型数据库 C# 数据库
技术笔记:MSCL超级工具类(C#),开发人员必备,开发利器
技术笔记:MSCL超级工具类(C#),开发人员必备,开发利器
55 3
|
6月前
|
开发框架 .NET 对象存储
【.NET Core】深入理解异步编程模型(APM)
【.NET Core】深入理解异步编程模型(APM)
120 1
|
6月前
|
Java BI C#
技术笔记:SM4加密算法实现Java和C#相互加密解密
技术笔记:SM4加密算法实现Java和C#相互加密解密
88 0
下一篇
无影云桌面