阅读《LEARNING HARD C#学习笔记》知识点总结与摘要五

简介:

本篇文章主要是总结异步编程的知识点,也是本系列的最后一篇文章,每一个知识点我都有写出示例代码,方便大家理解,若发现有误或不足之处还请指出,由于书中作者对此知识点讲解过于简单,所以在写这篇文章时本人参考与学习了网上许多大牛们的经验,在此感谢那些愿意分享的人们,谢谢!

二十三.异步编程

APM(异步编程模型):若类实现了返回类型为IAsyncResult接口的BeginXXX方法和EndXXX方法,则表明该类支持异步编程模型。如:委托类型定义了BeginInvoke与EndInvoke方法,所以所有的委托类型都实现了异步编程模型;

调用方法代码如下(以读取文件内容为例):

第一种方法(先调用BeginRead方法,再调用EndRead方法):

1
2
3
4
5
6
7
8
9
10
FileStream fs =  new  FileStream( "文件路径" , FileMode.Open);
             byte [] data =  new  byte [fs.Length];
             IAsyncResult result = fs.BeginRead(data, 0, data.Length, null null );
             fs.EndRead(result);
             fs.Close();
             fs.Dispose();
             System.Text.UTF8Encoding UTF8 =  new  System.Text.UTF8Encoding();
             string  readContent = UTF8.GetString(data);
             Console.WriteLine(readContent);
             Console.Read();

注意:调用EndRead方法时,会阻塞当前调用的线程,直到处理完成,若在UI线程中调用,将会出现UI假死现象;

第二种方法(先调用BeginRead方法,再通过IAsyncResult. AsyncWaitHandle得到WaitHandle对象,然后执行WaitHandle. WaitOne方法来等待异步执行完成,最后执行EndRead方法):

1
2
3
4
5
6
7
8
9
10
FileStream fs =  new  FileStream( "文件路径" , FileMode.Open);
             byte [] data =  new  byte [fs.Length];
             IAsyncResult result = fs.BeginRead(data, 0, data.Length,  null null );
             WaitHandle readWait=result.AsyncWaitHandle;
             readWait.WaitOne();
fs.EndRead(result);
             System.Text.UTF8Encoding UTF8 =  new  System.Text.UTF8Encoding();
             string  readContent = UTF8.GetString(data);
             Console.WriteLine(readContent);
             Console.Read();

注意:调用WaitOne方法时,会阻塞当前调用的线程,直到处理完成,若在UI线程中调用,将会出现UI假死现象;

第三种方法(先调用BeginRead方法,再循环判断IAsyncResult. IsCompleted,最后执行EndRead方法):

1
2
3
4
5
6
7
8
9
10
11
12
FileStream fs =  new  FileStream( "文件路径" , FileMode.Open);
             byte [] data =  new  byte [fs.Length];
             IAsyncResult result = fs.BeginRead(data, 0, data.Length,  null null );
             while (!result.IsCompleted)
             {
                 Thread.Sleep(1000);
             }
             fs.EndRead(result);
             System.Text.UTF8Encoding UTF8 =  new  System.Text.UTF8Encoding();
             string  readContent = UTF8.GetString(data);
             Console.WriteLine(readContent);
             Console.Read();

注意:循环判断IsComplete方法时,会阻塞当前调用的线程,直到处理完成[即:IsCompleted=true],若在UI线程中循环判断,将会出现UI假死现象;

第四种方法(先定义回调方法,然后再调用BeginRead方法,调用时传入异步回调方法委托实例及相关数据):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
FileStream fs =  new  FileStream( "文件路径" , FileMode.Open);
             byte [] data =  new  byte [fs.Length];
             IAsyncResult result = fs.BeginRead(data, 0, data.Length,  new  AsyncCallback(ReadCallback), data);
             Console.Read();
 
//回调方法
static  void  ReadCallback(IAsyncResult ar)
         {
             WaitHandle readWait = ar.AsyncWaitHandle;
             byte [] data =(  byte [])ar.AsyncState;
             System.Text.UTF8Encoding UTF8 =  new  System.Text.UTF8Encoding();
             string  readContent = UTF8.GetString(data);
             Console.WriteLine(readContent);
         }

说明:第四种由于采用异步委托方法获取得结果,所以不会阻塞调用线程,也就不会出现UI假死现象,当然前面三种方法也可以将耗时逻辑(等待及获取结果)放在方法中,然后在执行时开启新线程,这样可以达到与第四种相同的方法,但个人建议若在UI主线程中实现异步调用,优先考虑采用第四种方法;

 

EAP(基于事件的异步模式):实现了EAP的类具有一个或多个以Async为后缀的方法,以及对应的Completed事件,并支持异步方法的取消与进度报告。

EAP通过事件、AsyncOperationManager类和AsyncOperation类两个帮助器类实现如下功能:

1)   异步执行耗时的任务。

2)   获得进度报告和增量结果。

3)   支持耗时任务的取消。

4)   获得任务的结果值或异常信息。

5)   更复杂:支持同时执行多个异步操作、进度报告、增量结果、取消操作、返回结果值或异常信息。

对于相对简单的多线程应用程序,BackgroundWorker组件提供了一个简单的解决方案。对于更复杂的异步应用程序,可以考虑实现一个符合基于事件的异步模式的类。

可参见这篇博文:http://www.cnblogs.com/heyuquan/archive/2013/04/01/2993085.html

以下是BackgroundWorker组件在控制台程序中的使用方法:

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
56
57
58
59
60
61
62
63
64
65
66
67
68
69
using  System;
using  System.Collections.Generic;
using  System.ComponentModel;
using  System.Linq;
using  System.Text;
using  System.Threading.Tasks;
 
namespace  ConsoleApplication1
{
     class  Program
     {
         static  int  Main( string [] args)
         {
             Console.Write( "Main Thread ID:{0}\n" , System.Threading.Thread.CurrentThread.ManagedThreadId);
             int  workTimes = 100;
             BackgroundWorker bgWorker =  new  BackgroundWorker();
             bgWorker.WorkerSupportsCancellation =  true ; //设置支持异步取消
             bgWorker.WorkerReportsProgress =  true ; //设置支持报告进度
 
             bgWorker.DoWork += bgWorker_DoWork;
             bgWorker.ProgressChanged += bgWorker_ProgressChanged;
             bgWorker.RunWorkerCompleted += bgWorker_RunWorkerCompleted;
             bgWorker.RunWorkerAsync(workTimes); //启动异步执行
             string  input = Console.ReadLine();
             if  (input ==  "c"  && bgWorker.IsBusy)
             {
                 bgWorker.CancelAsync();
             }
             Console.Read();
             return  0;
         }
 
         static  void  bgWorker_DoWork( object  sender, DoWorkEventArgs e)
         {
             //在新线程中处理该方法
             BackgroundWorker bgWorker = sender  as  BackgroundWorker;
             int  workTimes = Convert.ToInt32(e.Argument);
             for ( int  i = 0; i <= workTimes;i++ )
             {
                 if  (bgWorker.CancellationPending)  //如果取消了
                 {
                    e.Cancel =  true ;
                    Console.Write( "ThreadID:{0}-->Cancel!\n" , System.Threading.Thread.CurrentThread.ManagedThreadId);
                     break ;
                 }
                 else
                 {
                     Console.Write( "Thread ID:{0}-->WorkTimes:{1}\n" , System.Threading.Thread.CurrentThread.ManagedThreadId, i);
                     bgWorker.ReportProgress(i); //投告进度,引发ProgressChanged事件
                     System.Threading.Thread.Sleep(1000);
                 }
             }
         }
 
 
         static  void  bgWorker_ProgressChanged( object  sender, ProgressChangedEventArgs e)
         {
             //当进度改变时在新线程中调用该方法,可直接操作控件
             Console.Write( "Thread ID:{0}-->Progress:{1}\n" , System.Threading.Thread.CurrentThread.ManagedThreadId, e.ProgressPercentage);
         }
 
 
         static  void  bgWorker_RunWorkerCompleted( object  sender, RunWorkerCompletedEventArgs e)
         {
             //当DoWork方法处理完成后(不论是否为取消)在新线程中调用此方法
             Console.Write( "Thread ID:{0}-->Completed!" , System.Threading.Thread.CurrentThread.ManagedThreadId);
         }
     }
}

TAP(基于任务的异步模式):一般使用Task类来实现基于任务的异步模式编程,实现步骤如下:

  1. 通过调用Task类指定的构造函数来实例化Task对象,如:Task task = new Task(Action委托实例,CancellationToken实例);注意:Task类有许多重载的构造函数,可依据实际情况进行调用
  2. 调用Task实例对象的Start方法启动异步执行构造函数参数中Action委托实例所引用的方法,如:task.Start()

可参见这篇博文:http://www.cnblogs.com/heyuquan/archive/2013/04/18/Task-based-Asynchronous-Pattern.html

具体的使用代码示例如下(为了让新手也能看懂,所以此处我采用标准的委托实例方法来写,大家可以使用Lambda表达式来写,那样代码量就会精简一些):

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
56
57
58
59
60
61
62
using  System;
using  System.Threading;
using  System.Threading.Tasks;
using  System.Windows.Forms;
 
namespace  WindowsFormsApplication1
{
     public  partial  class  Form4 : Form
     {
         CancellationTokenSource cts =  new  CancellationTokenSource();  //用于发出与接收任务取消信息
         public  Form4()
         {
             InitializeComponent();
         }
 
         private  void  Form4_Load( object  sender, EventArgs e)
         {
             TestTAP();
         }
 
         private  void  TestTAP()
         {
             WriteMsg( string .Format(  "Main Thread ID:{0}" , Thread.CurrentThread.ManagedThreadId));
             SynchronizationContext syncContext = SynchronizationContext.Current;  //获取当前同步上下文,用于在异步中可操作UI上的控件等
             Task task =  new  Task( new  Action< object >(TAPAsyncMethod), syncContext);
             task.Start();
         }
 
         private  void  TAPAsyncMethod( object  arg)
         {
             CancellationToken ct = cts.Token;
             SynchronizationContext sc = arg  as   SynchronizationContext;
 
             SendOrPostCallback callback =  new  SendOrPostCallback(WriteMsg);
             int  threadId = Thread.CurrentThread.ManagedThreadId;
 
             for  ( int  i = 1; i <= 100; i++)
             {
                 if  (ct.IsCancellationRequested)
                 {
                     sc.Post(callback,  string .Format( "Thread ID:{0}发出消息:任务取消了!" , threadId));
                     break ;
                 }
                 int  n = i + (i+1);
                 sc.Post(callback,  string .Format( "Thread ID:{0},发出消息:第{1}次执行结果:{2}" , threadId, i, n));
                 Thread.Sleep(500);
             }
         }
 
         private  void  WriteMsg( object  state)
         {
            listBox1.Items.Add( string .Format(  "Thread ID:{0}显示:[{1}]" , Thread.CurrentThread.ManagedThreadId, state));
            listBox1.SelectedIndex = listBox1.Items.Count - 1;
         }
 
         private  void  button1_Click( object  sender, EventArgs e)
         {
             //发送取消命令
             cts.Cancel();
         }
     }
}

C# 5.0中使用async和await关键字来实现异步编程,在这两个关键字的帮助下,我们可以使用同步编程的思想方式来实现异步编程,定义异步方法只需在定义普通方法的前面加上async关键字,在方法体指定的语句前加上await关键字实现异步处理耗时的代码。async和await关键字不会让调用的方法运行在新线程中,而是将方法以await关键字来作为界限分割成多个片段,并使其中的一些片段以异步方式执行。

注意:

1.只有标记了async关键字的方法或lambda表达式才能在方法体中使用await关键字;

2. 方法用async关键字标记不会影响方法是同步还是异步运行并完成,即:若方法用async关键字,但方法体中并没有包含await关键字,则仍以同步方式运行并完成;

3.只有返回类型为void、Task或Task<TResult>的方法才能用async关键字标记,但除如下情况:

A.程序的入口方法(Main方法);B.标记为同步的方法([MethodImpl(MethodImplOptions.Synchronized)]);C.包含ref或out参数的方法;D. Lambda被用作表达式树时;

4.只有方法的返回类型为可等待的类型(即类型必需包含公共的GetAwaiter() 方法并且返回有效的TaskAwaiter,如:Task、Task<TResult>)才能用await关键字标记;

可参见这篇博文:http://www.cnblogs.com/heyuquan/archive/2012/11/30/2795859.html

具体的使用代码示例如下:

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.Threading;
using  System.Threading.Tasks;
using  System.Windows.Forms;
 
namespace  WindowsFormsApplication1
{
     public  partial  class  Form5 : Form
     {
         public  Form5()
         {
             InitializeComponent();
         }
 
         private  void  button1_Click( object  sender, EventArgs e)
         {
             listBox1.Items.Add( string .Format( "Main Thread ID:{0}" , Thread.CurrentThread.ManagedThreadId));
             AsyncMethod();
         }
 
         private  async  void  AsyncMethod()
         {
             int  threadId = Thread.CurrentThread.ManagedThreadId;
             SynchronizationContext syncContext = SynchronizationContext.Current;
             await ExecBody(syncContext); //此处会开新线程,并且下面的代码将在该处执行完成后才会执行。
             listBox1.Items.Add( "方法执行完成了!" );
             listBox1.SelectedIndex = listBox1.Items.Count - 1;
         }
 
         private  Task ExecBody(SynchronizationContext sc)
         {
           return   Task.Run(() =>
             {
                 int  threadId = Thread.CurrentThread.ManagedThreadId;
                 for  ( int  i = 1; i <= 100; i++)
                 {
                     int  n = i + (i + 1);
                     sc.Post(state =>
                     {
                         listBox1.Items.Add( string .Format( "Thread ID:{0}显示:[{1}]" , Thread.CurrentThread.ManagedThreadId, state));
                         listBox1.SelectedIndex = listBox1.Items.Count - 1;
                     },  string .Format( "Thread ID:{0},发出消息:第{1}次执行结果:{2}" , threadId, i, n));
                     Thread.Sleep(500);
                 }
             });
         }
     }
}
本文转自 梦在旅途 博客园博客,原文链接:http://www.cnblogs.com/zuowj/p/4508587.html   ,如需转载请自行联系原作者
相关文章
|
5月前
|
机器学习/深度学习 自然语言处理 算法
【论文】SimCLS:一个简单的框架 摘要总结的对比学习(1)
【论文】SimCLS:一个简单的框架 摘要总结的对比学习(1)
48 0
|
11月前
|
机器学习/深度学习 人工智能 自然语言处理
Prompt learning 教学[进阶篇]:简介Prompt框架并给出自然语言处理技术:Few-Shot Prompting、Self-Consistency等;项目实战搭建知识库内容机器人
Prompt learning 教学[进阶篇]:简介Prompt框架并给出自然语言处理技术:Few-Shot Prompting、Self-Consistency等;项目实战搭建知识库内容机器人
Prompt learning 教学[进阶篇]:简介Prompt框架并给出自然语言处理技术:Few-Shot Prompting、Self-Consistency等;项目实战搭建知识库内容机器人
|
8月前
|
C#
《More Effective C# 》读书笔记 第一章
《More Effective C# 》读书笔记 第一章
|
机器学习/深度学习 存储 缓存
计算机科学速成课 Crash Course Computer Science 笔记(摘要形式)
计算机科学速成课 Crash Course Computer Science 笔记(摘要形式)
367 0
|
数据处理
主题论文总结3:维基百科生成任务(持续更新ing...)
主题论文总结3:维基百科生成任务(持续更新ing...)
|
机器学习/深度学习 自然语言处理 数据库
阅读CHOLAN论文总结
CHOLAN是一种模块化的实体链接方法,由两个transformer-based模型,按顺序集成的pipeline模型。 第一个transformer模型:得到文本中的entity mention。 第二个transformer模型:处理第一个得到的mention,获取其上下文和实体描述,对mention进行分类,放到对应实体的候选列表。
|
搜索推荐
How to Read a Paper (怎样阅读论文)
![在这里插入图片描述](https://img-blog.csdnimg.cn/20191230093902925.png) ### 0. Three Pass Approach 作者介绍了一个"Three pass"的看论文阅读方法,目的是为了在人们阅读论文细节之前有一个大体的掌握。 第一遍是掌握论文的大体意思; 第二遍是查阅论文的主题,但是不看论文的细节; 第三遍是帮助你更佳深度的了解
112 1
How to Read a Paper (怎样阅读论文)
撒花!吴恩达《Machine Learning Yearning》完结!
撒花!吴恩达《Machine Learning Yearning》完结!
撒花!吴恩达《Machine Learning Yearning》完结!
|
算法 图计算 知识图谱
图嵌入综述 (arxiv 1709.07604) 译文第三章
原文:A Comprehensive Survey of Graph Embedding: Problems, Techniques and Applications (arxiv 1709.07604) 译者:飞龙 协议:CC BY-NC-SA 4.0 自豪地采用谷歌翻译 三、图嵌入的问题设定 在本节中,我们从问题设定的角度比较现有的图嵌入工作,其中包括嵌入输入和嵌入输出。