1. 程式人生 > >[C#學習筆記之異步編程模式2]BeginInvoke和EndInvoke方法 (轉載)

[C#學習筆記之異步編程模式2]BeginInvoke和EndInvoke方法 (轉載)

cti otf 函數返回 編程模式 catch 數值 gin 單線程 blog

為什麽要進行異步回調?眾所周知,普通方法運行,是單線程的,如果中途有大型操作(如:讀取大文件,大批量操作數據庫,網絡傳輸等),都會導致方法阻塞,表現在界面上就是,程序卡或者死掉,界面元素不動了,不響應了。異步方法很好的解決了這些問題,異步執行某個方法,程序立即開辟一個新線程去運行你的方法,主線程包括界面就不會死掉了。異步調用並不是要減少線程的開銷, 它的主要目的是讓調用方法的主線程不需要同步等待在這個函數調用上, 從而可以讓主線程繼續執行它下面的代碼.

BeginInvoke方法可以使用線程異步地執行委托所指向的方法。然後通過EndInvoke方法獲得方法的返回值(EndInvoke方法的返回值就是被調用方法的返回值),或是確定方法已經被成功調用。當使用BeginInvoke異步調用方法時,如果方法未執行完,EndInvoke方法就會一直阻塞,直到被調用的方法執行完畢。


異步調用通用模板

//…… //普通的代碼:處於同步執行模式 IAsyncResultret=委托變量.BeginInvoke(……); //啟動異步調用 //可以在這一部分幹其他一些事,程序處於異步執行模式 用於保存方法結果的變量=委托變量.EndInvoke(ret); //結束異步調用 //普通的代碼:處於同步執行模式 //…… 對照上一篇文章中的計算指定文件夾的容量的例子(例2) [csharp] view plaincopyprint?
  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. }

異步調用的奧秘

異步調用是通過委托來進行的,我們看看是如何定義委托的: [csharp] view plaincopyprint?
  1. public delegate long CalculateFolderSizeDelegate(string FolderName);
通過Reflactor反編譯結果如下: [csharp] view plaincopyprint?
  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來判斷異步調用是否完成,並顯示一些提示信息。這樣做可以增加用戶體驗。代碼如下: [csharp] view plaincopyprint?
  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 [csharp] view plaincopyprint?
  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.使用異步回調函數 [csharp] view plaincopyprint?
  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補充 對於上面最後一個異步回調的例子有一個缺陷,就是當異步調用的函數與主線程都需要訪問同一資源時,要註意解決資源共享的問題。如下圖: 技術分享 修改程序如下: [csharp] view plaincopyprint?
  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方法 (轉載)