为什么要进行异步回调?众所周知,普通方法运行,是单线程的,如果中途有大型操作(如:读取大文件,大批量操作数据库,网络传输等),都会导致方法阻塞,表现在界面上就是,程序卡或者死掉,界面元素不动了,不响应了。异步方法很好的解决了这些问题,异步执行某个方法,程序立即开辟一个新线程去运行你的方法,主线程包括界面就不会死掉了。异步调用并不是要减少线程的开销, 它的主要目的是让调用方法的主线程不需要同步等待在这个函数调用上, 从而可以让主线程继续执行它下面的代码.

BeginInvoke方法可以使用线程异步地执行委托所指向的方法。然后通过EndInvoke方法获得方法的返回值(EndInvoke方法的返回值就是被调用方法的返回值),或是确定方法已经被成功调用。当使用BeginInvoke异步调用方法时,如果方法未执行完,EndInvoke方法就会一直阻塞,直到被调用的方法执行完毕。

异步调用通用模板

 
//……
//普通的代码:处于同步执行模式
IAsyncResultret=委托变量.BeginInvoke(……); //启动异步调用
//可以在这一部分干其他一些事,程序处于异步执行模式
用于保存方法结果的变量=委托变量.EndInvoke(ret); //结束异步调用
//普通的代码:处于同步执行模式
//……
 
对照上一篇文章中的计算指定文件夹的容量的例子(例2)
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.IO;
  5. namespace AsyncCalculateFolderSize1
  6. {
  7. class Program
  8. {
  9. //计算指定文件夹的总容量
  10. private static long CalculateFolderSize(string FolderName)
  11. {
  12. if (Directory.Exists(FolderName) == false)
  13. {
  14. throw new DirectoryNotFoundException("文件夹不存在");
  15. }
  16. DirectoryInfo RootDir = new DirectoryInfo(FolderName);
  17. //获取所有的子文件夹
  18. DirectoryInfo[] ChildDirs = RootDir.GetDirectories();
  19. //获取当前文件夹中的所有文件
  20. FileInfo[] files = RootDir.GetFiles();
  21. long totalSize = 0;
  22. //累加每个文件的大小
  23. foreach (FileInfo file in files)
  24. {
  25. totalSize += file.Length;
  26. }
  27. //对每个文件夹执行同样的计算过程:累加其下每个文件的大小
  28. //这是通过递归调用实现的
  29. foreach (DirectoryInfo dir in ChildDirs)
  30. {
  31. totalSize += CalculateFolderSize(dir.FullName);
  32. }
  33. //返回文件夹的总容量
  34. return totalSize;
  35. }
  36. //定义一个委托
  37. public delegate long CalculateFolderSizeDelegate(string FolderName);
  38. static void Main(string[] args)
  39. {
  40. //定义一个委托变量引用静态方法CalculateFolderSize
  41. CalculateFolderSizeDelegate d = CalculateFolderSize;
  42. Console.WriteLine("请输入文件夹名称(例如:C:\\Windows):");
  43. string FolderName = Console.ReadLine();
  44. //通过委托异步调用静态方法CalculateFolderSize
  45. IAsyncResult ret=d.BeginInvoke(FolderName,null,null);
  46. Console.WriteLine("正在计算中,请耐心等待……");
  47. //阻塞,等到调用完成,取出结果
  48. long size = d.EndInvoke(ret);
  49. Console.WriteLine("\n计算完成。文件夹{0}的容量为:{1}字节\n", FolderName, size);
  50. }
  51. }
  52. }

异步调用的奥秘

异步调用是通过委托来进行的,我们看看是如何定义委托的:
  1. public delegate long CalculateFolderSizeDelegate(string FolderName);
通过Reflactor反编译结果如下:                                    
  1. public sealed class CalculateFolderSizeDelegate: MulticastDelegate
  2. {
  3. public CalculateFolderSizeDelegate(Object target , intmethodPtr)
  4. { …… }
  5. public virtual long invoke(string FolderName)
  6. { …… }
  7. public virtual IAsyncResult BeginInvoke( string FolderName,
  8. AsyncCallbackcallback , object asyncState)
  9. { …… }
  10. public virtual long EndInvoke( IAsyncResultresult )
  11. { …… }
  12. }

由此我们发现,当我们定义一个委托的时候,实际上是定义了一个委托类型,这个类型有invoke、BeginInvoke()、EndInvoke()这样几个成员方法,而这几个成员方法可以实现一步调用机制。我们看看这几个方法格式怎么定义的:

 

(1)BeginInvoke方法用于启动异步调用

 

BeginInvoke()的函数声明:

public IAsyncResult BeginInvoke(

<输入和输出变量>,回调函数callback , 附加信息AsyncState)

函数返回值类型:

public interface IAsyncResult

{

object AsyncState{ get;}  //如果有回调函数的话该参数用于保存要传递给回调函数的参数值

WaitHandle AsyncWaitHandle{ get;}

bool CompletedSynchronously{ get;}

bool IsCompleted{ get;} //保存方法是否执行结束,我们可以通过该属性的值来判断异步方法是否执行结束

}

1.BeginInvoke返回IasyncResult,可用于监视调用进度。

2.结果对象IAsyncResult是从开始操作返回的,并且可用于获取有关异步开始操作是否已完成的状态。

3.结果对象被传递到结束操作,该操作返回调用的最终返回值。

4.在开始操作中可以提供可选的回调。如果提供回调,在调用结束后,将调用该回调;并且回调中的代码可以调用结束操作。

5.如果需要将一些额外的信息传送给回调函数,就将其放入BeginInvoke()方法的第3个参数asyncState中。注意到这个参数的类型为Object,所以可以放置任意类型的数据。如果有多个信息需要传送给回调函数,可以将所有要传送的信息封状到一个Struct变量,或者干脆再定义一个类,将信息封装到这个类所创建的对象中,再传送给BeginInvoke()方法。

(2)EndInvoke方法用于检索异步调用结果。

方法声明:

public <方法返回值类型>EndInvoke(<声明为ref或out的参数>, IAsyncResult result )

1.result参数由BeginInvoke()方法传回。.NET借此以了解方法调用是否完成。

2.当EndInvoke方法发现异步调用完成时,它取出此异步调用方法的返回值作为其返回值,如果异步调用方法有声明为ref和out的参数,它也负责填充它。

3.在调用BeginInvoke后可随时调用EndInvoke方法,注意:始终在异步调用完成后调用EndInvoke.
4.如果异步调用未完成,EndInvoke将一直阻塞到异步调用完成。
5.EndInvoke的参数包括需要异步执行的方法的out和ref参数以及由BeginInvoke返回的IAsyncResult。

应用实例:

 
1.使用轮询等待异步调用完成:使用IAsyncResult的IsCompleted属性来判断异步调用是否完成
 
 虽然上面的方法可以很好地实现异步调用,但是当调用EndInvoke方法获得调用结果时,整个程序就象死了一样,依然要等待异步方法执行结束,这样做用户的感觉并不会太  好,因此,我们可以使用 asyncResult来判断异步调用是否完成,并显示一些提示信息。这样做可以增加用户体验。代码如下:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.IO;
  5. namespace AsyncCalculateFolderSize2
  6. {
  7. class Program
  8. {
  9. //计算指定文件夹的总容量
  10. private static long CalculateFolderSize(string FolderName)
  11. {
  12. if (Directory.Exists(FolderName) == false)
  13. {
  14. throw new DirectoryNotFoundException("文件夹不存在");
  15. }
  16. DirectoryInfo RootDir = new DirectoryInfo(FolderName);
  17. //获取所有的子文件夹
  18. DirectoryInfo[] ChildDirs = RootDir.GetDirectories();
  19. //获取当前文件夹中的所有文件
  20. FileInfo[] files = RootDir.GetFiles();
  21. long totalSize = 0;
  22. //累加每个文件的大小
  23. foreach (FileInfo file in files)
  24. {
  25. totalSize += file.Length;
  26. }
  27. //对每个文件夹执行同样的计算过程:累加其下每个文件的大小
  28. //这是通过递归调用实现的
  29. foreach (DirectoryInfo dir in ChildDirs)
  30. {
  31. totalSize += CalculateFolderSize(dir.FullName);
  32. }
  33. //返回文件夹的总容量
  34. return totalSize;
  35. }
  36. //定义一个委托
  37. public delegate long CalculateFolderSizeDelegate(string FolderName);
  38. static void Main(string[] args)
  39. {
  40. //定义一个委托变量引用静态方法CalculateFolderSize
  41. CalculateFolderSizeDelegate d = CalculateFolderSize;
  42. Console.WriteLine("请输入文件夹名称(例如:C:\\Windows):");
  43. string FolderName = Console.ReadLine();
  44. //通过委托异步调用静态方法CalculateFolderSize
  45. IAsyncResult ret = d.BeginInvoke(FolderName, null, null);
  46. Console.Write ("正在计算中,请耐心等待");
  47. //每隔2秒检查一次,输出一个“."
  48. while (ret.IsCompleted == false)
  49. {
  50. Console.Write(".");
  51. System.Threading.Thread.Sleep(200);
  52. }
  53. //阻塞,等到调用完成,取出结果
  54. long size = d.EndInvoke(ret);
  55. Console.WriteLine("\n计算完成!\n文件夹{0}的容量为:{1}字节", FolderName, size);
  56. }
  57. }
  58. }

这样,当程序在执行CalculateFolderSize这个异步方法的时候主线程并不是“假死”,而是每隔0.2毫秒输出一个“.",这就是异步调用的妙处!

这里需要用到BeginInvoke的返回值IAsyncResult的IsCompleted这个属性来判断异步线程是否执行结束。
2. 使用轮询等待异步调用完成:使用IAsyncResult的AsyncWaitHandle.WaitOne
 
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.IO;
  6. namespace AsyncCalculateFolderSize3
  7. {
  8. class Program
  9. {
  10. //计算指定文件夹的总容量
  11. private static long CalculateFolderSize(string FolderName)
  12. {
  13. if (Directory.Exists(FolderName) == false)
  14. {
  15. throw new DirectoryNotFoundException("文件夹不存在");
  16. }
  17. DirectoryInfo RootDir = new DirectoryInfo(FolderName);
  18. //获取所有的子文件夹
  19. DirectoryInfo[] ChildDirs = RootDir.GetDirectories();
  20. //获取当前文件夹中的所有文件
  21. FileInfo[] files = RootDir.GetFiles();
  22. long totalSize = 0;
  23. //累加每个文件的大小
  24. foreach (FileInfo file in files)
  25. {
  26. totalSize += file.Length;
  27. }
  28. //对每个文件夹执行同样的计算过程:累加其下每个文件的大小
  29. //这是通过递归调用实现的
  30. foreach (DirectoryInfo dir in ChildDirs)
  31. {
  32. totalSize += CalculateFolderSize(dir.FullName);
  33. }
  34. //返回文件夹的总容量
  35. return totalSize;
  36. }
  37. //定义一个委托
  38. public delegate long CalculateFolderSizeDelegate(string FolderName);
  39. static void Main(string[] args)
  40. {
  41. //定义一个委托变量引用静态方法CalculateFolderSize
  42. CalculateFolderSizeDelegate d = CalculateFolderSize;
  43. Console.WriteLine("请输入文件夹名称(例如:C:\\Windows):");
  44. string FolderName = Console.ReadLine();
  45. //通过委托异步调用静态方法CalculateFolderSize
  46. IAsyncResult ret = d.BeginInvoke(FolderName, null, null);
  47. Console.Write("正在计算中,请耐心等待");
  48. while(!ret.AsyncWaitHandle.WaitOne(2000))
  49. {
  50. //等待2秒钟,输出一个“.”
  51. Console.Write(".");
  52. }
  53. //阻塞,等到调用完成,取出结果
  54. long size = d.EndInvoke(ret);
  55. Console.WriteLine("\n计算完成。文件夹{0}的容量为:{1}字节\n", FolderName, size);
  56. }
  57. }
  58. }
 WaitOne的第一个参数表示要等待的毫秒数,在指定时间之内,WaitOne方法将一直等待,直到异步调用完成,并发出通知,WaitOne方法才返回true。当等待指定时间之后,异步调用仍未完成,WaitOne方法返回false,如果指定时间为0,表示不等待,如果为-1,表示永远等待,直到异步调用完成。

3.使用异步回调函数

  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.IO;
  5. namespace AsyncCalculateFolderSize4
  6. {
  7. class Program
  8. {
  9. //计算指定文件夹的总容量
  10. private static long CalculateFolderSize(string FolderName)
  11. {
  12. if (Directory.Exists(FolderName) == false)
  13. {
  14. throw new DirectoryNotFoundException("文件夹不存在");
  15. }
  16. DirectoryInfo RootDir = new DirectoryInfo(FolderName);
  17. //获取所有的子文件夹
  18. DirectoryInfo[] ChildDirs = RootDir.GetDirectories();
  19. //获取当前文件夹中的所有文件
  20. FileInfo[] files = RootDir.GetFiles();
  21. long totalSize = 0;
  22. //累加每个文件的大小
  23. foreach (FileInfo file in files)
  24. {
  25. totalSize += file.Length;
  26. }
  27. //对每个文件夹执行同样的计算过程:累加其下每个文件的大小
  28. //这是通过递归调用实现的
  29. foreach (DirectoryInfo dir in ChildDirs)
  30. {
  31. totalSize += CalculateFolderSize(dir.FullName);
  32. }
  33. //返回文件夹的总容量
  34. return totalSize;
  35. }
  36. public delegate long CalculateFolderSizeDelegate(string FolderName);
  37. private static CalculateFolderSizeDelegate task = CalculateFolderSize;
  38. //用于回调的函数
  39. public static void ShowFolderSize(IAsyncResult result)
  40. {
  41. long size = task.EndInvoke(result);
  42. Console.WriteLine("\n文件夹{0}的容量为:{1}字节\n", (String)result.AsyncState, size);
  43. }
  44. static void Main(string[] args)
  45. {
  46. string FolderName;
  47. while (true)
  48. {
  49. Console.WriteLine("请输入文件夹名称(例如:C:\\Windows),输入quit结束程序");
  50. FolderName = Console.ReadLine();
  51. if (FolderName == "quit")
  52. break;
  53. task.BeginInvoke(FolderName, ShowFolderSize, FolderName);//第一个参数是异步函数的参数,第二个参数是回调函数,第三个参数是回调函数的参数,回调函数会在异步函数执行结束之后被调用。
  54. }
  55. }
  56. }
  57. }

这个例子中通过循环的输入文件夹名称计算文件夹容量,计算的操作放在异步调用函数中,因此我们在输入下一个文件夹名称时不必等待上一个计算结束,异步函数执行完成之后会自动调用回调函数ShowFolderSize进行结果处理。

(今天就学到这里,下午去嘉禾看期待已久的3D《复仇者联盟》,吼吼......)
2012/5/20 23:05补充
 
对于上面最后一个异步回调的例子有一个缺陷,就是当异步调用的函数与主线程都需要访问同一资源时,要注意解决资源共享的问题。如下图:
修改程序如下:
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Text;
  4. using System.IO;
  5. namespace AsyncCalculateFolderSize6
  6. {
  7. class Program
  8. {
  9. //计算指定文件夹的总容量
  10. private static long CalculateFolderSize(string FolderName)
  11. {
  12. if (Directory.Exists(FolderName) == false)
  13. {
  14. throw new DirectoryNotFoundException("文件夹不存在");
  15. }
  16. DirectoryInfo RootDir = new DirectoryInfo(FolderName);
  17. //获取所有的子文件夹
  18. DirectoryInfo[] ChildDirs = RootDir.GetDirectories();
  19. //获取当前文件夹中的所有文件
  20. FileInfo[] files = RootDir.GetFiles();
  21. long totalSize = 0;
  22. //累加每个文件的大小
  23. foreach (FileInfo file in files)
  24. {
  25. totalSize += file.Length;
  26. }
  27. //对每个文件夹执行同样的计算过程:累加其下每个文件的大小
  28. //这是通过递归调用实现的
  29. foreach (DirectoryInfo dir in ChildDirs)
  30. {
  31. totalSize += CalculateFolderSize(dir.FullName);
  32. }
  33. //返回文件夹的总容量
  34. return totalSize;
  35. }
  36. //定义一个委托
  37. public delegate long CalculateFolderSizeDelegate(string FolderName);
  38. private static CalculateFolderSizeDelegate d = new CalculateFolderSizeDelegate(CalculateFolderSize);
  39. //用于回调的函数
  40. public static void ShowFolderSize(IAsyncResult result)
  41. {
  42. try
  43. {
  44. long size = d.EndInvoke(result);
  45. while (Console.CursorLeft != 0)//只有用户不输入,且光标位于第一列时,才输出信息。
  46. {
  47. //等待2秒
  48. System.Threading.Thread.Sleep(2000);
  49. }
  50. Console.WriteLine("\n文件夹{0}的容量为:{1}字节\n", (String)result.AsyncState, size);
  51. }
  52. catch (DirectoryNotFoundException e)
  53. {
  54. Console.WriteLine("您输入的文件夹不存在");
  55. }
  56. }
  57. static void Main(string[] args)
  58. {
  59. string FolderName;
  60. while (true)
  61. {
  62. Console.WriteLine("请输入文件夹名称(例如:C:\\Windows),输入quit结束程序");
  63. FolderName = Console.ReadLine();
  64. if (FolderName == "quit")
  65. break;
  66. d.BeginInvoke(FolderName, ShowFolderSize, FolderName);
  67. }
  68. }
  69. }
  70. }

Invoke() 调用时,会阻塞当前线程,等到 Invoke() 方法返回才继续执行后面的代码,表现出“同步”的概念。
BeginInvoke() 调用时,当前线程会启用线程池中的某个线程来执行此方法,当前线程不被阻塞,继续运行后面的代码,表现出“异步”的概念。
EndInvoke() ,在想获取 BeginInvoke() 执行完毕后的结果时,调用此方法来获取。

 
code:
 DevExpress.Utils.WaitDialogForm wf = new DevExpress.Utils.WaitDialogForm("请等待......", "正在导出到" + dlg.FileName, new Size(300, 80));
 DbfHelperDelegate d = new DbfHelperDelegate(dbf.CreateDBF);
 IAsyncResult ia = d.BeginInvoke(dlg.FileName, gridView, null, null);
  if (d.EndInvoke(ia))
      wf.Close();
 
文章转载地址:
http://m.blog.csdn.net/article/details?id=7971082&from=singlemessage

[C#学习笔记之异步编程模式2]BeginInvoke和EndInvoke方法 (转载)的更多相关文章

  1. 异步使用委托delegate --- BeginInvoke和EndInvoke方法

    当我们定义一个委托的时候,一般语言运行时会自动帮委托定义BeginInvoke 和 EndInvoke两个方法,这两个方法的作用是可以异步调用委托. 方法BeginInvoke有两个参数: Async ...

  2. Python学习笔记之面向对象编程(三)Python类的魔术方法

    python类中有一些方法前后都有两个下划线,这类函数统称为魔术方法.这些方法有特殊的用途,有的不需要我们自己定义,有的则通过一些简单的定义可以实现比较神奇的功能 我主要把它们分为三个部分,下文也是分 ...

  3. 多线程编程学习笔记——使用异步IO(一)

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  4. 多线程编程学习笔记——使用异步IO

    接上文 多线程编程学习笔记——使用并发集合(一) 接上文 多线程编程学习笔记——使用并发集合(二) 接上文 多线程编程学习笔记——使用并发集合(三) 假设以下场景,如果在客户端运行程序,最的事情之一是 ...

  5. 【专栏学习】APM——异步编程模型(.NET不推荐)

    (1)learning hard C#学习笔记 异步1:<learning hard C#学习笔记>读书笔记(20)异步编程 (2)<C# 4.0 图解教程> 22.4 异步编 ...

  6. ASP.NET MVC 学习笔记-7.自定义配置信息 ASP.NET MVC 学习笔记-6.异步控制器 ASP.NET MVC 学习笔记-5.Controller与View的数据传递 ASP.NET MVC 学习笔记-4.ASP.NET MVC中Ajax的应用 ASP.NET MVC 学习笔记-3.面向对象设计原则

    ASP.NET MVC 学习笔记-7.自定义配置信息   ASP.NET程序中的web.config文件中,在appSettings这个配置节中能够保存一些配置,比如, 1 <appSettin ...

  7. 孙鑫VC学习笔记:多线程编程

    孙鑫VC学习笔记:多线程编程 SkySeraph Dec 11st 2010  HQU Email:zgzhaobo@gmail.com    QQ:452728574 Latest Modified ...

  8. Hadoop学习笔记(7) ——高级编程

    Hadoop学习笔记(7) ——高级编程 从前面的学习中,我们了解到了MapReduce整个过程需要经过以下几个步骤: 1.输入(input):将输入数据分成一个个split,并将split进一步拆成 ...

  9. WCF学习笔记之事务编程

    WCF学习笔记之事务编程 一:WCF事务设置 事务提供一种机制将一个活动涉及的所有操作纳入到一个不可分割的执行单元: WCF通过System.ServiceModel.TransactionFlowA ...

随机推荐

  1. Swift - 反射(Reflection)的介绍与使用样例(附KVC介绍)

    1,反射(Reflection) 对于C#.Java开发人员来说,肯定都对反射这个概念相当熟悉.所谓反射就是可以动态获取类型.成员信息,同时在运行时(而非编译时)可以动态调用任意方法.属性等行为的特性 ...

  2. Spring cloud服务的提供者建立

    1.0我们要在这里要建立一个服务的提供者模块,是一个module,类似于服务的公共模块 第一步:添加架包 <project xmlns="http://maven.apache.org ...

  3. C# html代码生成word

    首先引入 Microsoft.Office.Interop.Word 其次要先说一下,把一大段html代码直接变成word文件,只能生成doc文件,docx文件应该是不行的 首先我们用IO生成一个do ...

  4. 解决AttributeError: 'module' object has no attribute 'main' 安装第三方包报错

    1.找到pycharm 目录下的 \helper\packaging_tool.py 文件 2.用新版pycharm 的packaging_tool.py 替换 旧版 同名文件 文件代码如下: imp ...

  5. 实验一:JAVA实验环境搭建 ,JDK下载与安装及 Eclipse下载与安装

    一.搭建JAVA实验环境 1.JDK的下载 (1)打开 IE 浏览器,输入网址“http://www.oracle.com/index.html”,浏览 Oracle 官方主页.鼠标双击Downloa ...

  6. MySQL SQL模式特点汇总

    前言 MySQL服务器可以在不同的SQL模式下运行,并且可以针对不同的客户端以不同的方式应用这些模式,具体取决于sql_mode系统变量的值.DBA可以设置全局SQL模式以匹配站点服务器操作要求,并且 ...

  7. 深入理解B/S与C/S架构

    首先来介绍一下B/S与C/S架构 C/S架构简要介绍 在了解什么是B/S架构之前,我们有必要了解一下什么是C/S架构: C/S架构是第一种比较早的软件架构,主要用于局域网内.也叫 客户机/服务器模式. ...

  8. 利用Tensorflow实现手写字符识别

    模式识别领域应用机器学习的场景非常多,手写识别就是其中一种,最简单的数字识别是一个多类分类问题,我们借这个多类分类问题来介绍一下google最新开源的tensorflow框架,后面深度学习的内容都会基 ...

  9. java8新特性:利用Lambda处理List集合

    Java 8新增的Lambda表达式,我们可以用简洁高效的代码来处理List. 1.遍历 public static void main(String[] args) { List<User&g ...

  10. 在数据表中字段存有excel、word文件数据,让其随着记录指针移动而改变显示

    这是一个及其简单的问题,可是被绕住了.当记录移动时,装载数据到excel显示数据.这个方法写在哪里?如何触发? datasource.cds等空间的触发都是多次.到现在也没明白如何去做.笨方法: 1. ...