.net基础---异步编程

1.概念:

异步编程是建立在委托基础之上的一种编程方法,异步调用的每个方法都在独立的线程中执行,因此,其本质上就是一种多线程程序,可以看成是应用了"简化版本"的多线程技术

2.程序的同步执行和异步执行:

(1)同步编程方式:

程序中代码是按顺序执行的,如果在代码中调用一个函数,则必须等待函数的所有代码执行完毕之后,才能回到原来的地方执行下一行代码,这种程序运行方式成为"同步"

实例:做一个读取文件夹容量的程序

 1  static void Main(string[] args)
 2         {
 3 
 4             Console.WriteLine("请输入文件夹名称:");
 5             string folderName = Console.ReadLine();
 6             long totalSize= CalcFolderSize(folderName);
 7             Console.WriteLine("正在读取,请稍后...");
 8             Console.WriteLine("文件夹容量为:{0}",totalSize);
 9             Console.ReadKey();
10         }
11 private static long CalcFolderSize(string folderName)
12         {
13             try
14             {
15                 if (!Directory.Exists(folderName))
16                     return 0;
17                 DirectoryInfo rootDir = new DirectoryInfo(folderName);
18                 DirectoryInfo[] childDirs = rootDir.GetDirectories();
19                 FileInfo[] files = rootDir.GetFiles();
20                 long totalSize = 0;
21                 foreach (FileInfo file in files)
22                 {
23                     totalSize += file.Length;
24                 }
25                 foreach (DirectoryInfo dir in childDirs)
26                 {
27                     totalSize += CalcFolderSize(dir.FullName);
28                 }
29                 return totalSize;
30             }
31             catch (Exception ex)
32             {
33                 Console.WriteLine(ex.Message);
34                 return 0;
35             }
36         }

程序运行到第6行,如果函数不返回结果,第7行是不能执行的,如果CalcFolderSize这个函数中读取的文件夹嵌套的很多,那么这个函数的执行时间就会很长,给用户的感觉就像是程序卡死了,而且第7行的友好提示也显示不出来。如果想不用等待函数执行完就执行下一行代码,就必须采用异步编程。

(2)异步编程模式

我们改造上面的代码,执行函数无需等待就能执行下一行的友好提示信息:

 

 1  delegate long CalcFolderSizeHandler(string folderName);
 2  static void Main(string[] args)
 3         {
 4             Console.WriteLine("请输入文件夹名称:");
 5             string folderName = Console.ReadLine();
 6             CalcFolderSizeHandler handler = new CalcFolderSizeHandler(CalcFolderSize);
 7             IAsyncResult result = handler.BeginInvoke(folderName, null, null);
 8             Console.WriteLine("正在读取,请稍后...");
 9             long totalSize = handler.EndInvoke(result);
10             Console.WriteLine(string.Format("文件夹容量为:{0}", totalSize));
11             Console.ReadKey();
12         }
13  private static long CalcFolderSize(string folderName)
14         {
15             try
16             {
17                 if (!Directory.Exists(folderName))
18                     return 0;
19                 DirectoryInfo rootDir = new DirectoryInfo(folderName);
20                 DirectoryInfo[] childDirs = rootDir.GetDirectories();
21                 FileInfo[] files = rootDir.GetFiles();
22                 long totalSize = 0;
23                 foreach (FileInfo file in files)
24                 {
25                     totalSize += file.Length;
26                 }
27                 foreach (DirectoryInfo dir in childDirs)
28                 {
29                     totalSize += CalcFolderSize(dir.FullName);
30                 }
31                 return totalSize;
32             }
33             catch (Exception ex)
34             {
35                 Console.WriteLine(ex.Message);
36                 return 0;
37             }
38         }

 

 注意上面加红的代码。

异步调用的关键在于函数CalcFolderSize不是在主线程(Main函数所在的线程)中执行的,而是在另一个辅助线程中与主线程并行执行的,由于存在两个并行执行的线程,所以执行函数CalcFolderSize之后,不会等待其完成,而是马上执行下一行代码,输出友好提示信息。

方法启动之后,必须由办法取回计算结果,EndInvoke()方法可以完成这一任务,但他需要一些额外的信息,这些信息是BeginInvoke()方法启动一个异步调用时提供的IAsynResult类型的对象。

如果CalcFolderSize没有返回,他就会停在EndInvoke()处等待。

3.异步调用模式揭秘

(1)BeginInvoke()方法:

 public IAsyncResult BeginInvoke( <输出和输入变量>, AsyncCallback callback, object asyncState ) 

参数的第一部分是定义委托时确定的方法签名中的参数列表(可能不止一个),下面是msdn上的例子

 public delegate string AsyncMethodCaller(int callDuration, out int threadId);
 public string TestMethod(int callDuration, out int threadId) 
        {
            Console.WriteLine("Test method begins.");
            Thread.Sleep(callDuration);
            threadId = Thread.CurrentThread.ManagedThreadId;
            return String.Format("My call time was {0}.", callDuration.ToString());
        }
//调用
            // Create the delegate.
            AsyncMethodCaller caller = new AsyncMethodCaller(ad.TestMethod);

            // Initiate the asychronous call.
            IAsyncResult result = caller.BeginInvoke(3000, 
                out threadId, null, null);

            Thread.Sleep(0);
            Console.WriteLine("Main thread {0} does some work.",
                Thread.CurrentThread.ManagedThreadId);

            // Wait for the WaitHandle to become signaled.
            result.AsyncWaitHandle.WaitOne();

            // Perform additional processing here.
            // Call EndInvoke to retrieve the results.
            string returnValue = caller.EndInvoke(out threadId, result);

 

第二部分参数callback是当异步调用结束时自动回调的函数,第三个函数asyncstate用于向第二个参数所确定的callback回调函数提供额外的信息,比如,如果回调函数需要参数,则asyncState可用于提供这些参数,asyncState是object类型的所以他使用比较灵活

(2)EndInvoke方法

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

BeginInvoke方法负责生成一个IAsyncResult接口类型的对象并填充其字段,这个IAsyncResult对象可以看成一个"望远镜",启动异步调用的调用者线程通过它可以跨越线程边界,远距离的了解正在另一个线程上运行的方法。

借助IAsyncResult对象,EndInvoke方法不断的查询异步调用方法是否执行完毕,这样启动异步调用的调用者线程就可以获取异步调用的方法结果了。正由于EndInvoke方法由一个不断轮训的过程,所以主线程程序在执行到调用EndInvoke方法时会暂停,等待异步调用方法完成,取回结果后再继续运行。

4.异步回调

EndInvoke会阻塞主线程等待结果返回,这样无疑会浪费CPU时间在循环上等待,能不能让异步调用方法在结束时自动调用一个函数处理结果?使用异步回调可以满足这个要求。

修改上面的代码:

 1 class Program
 2     {
 3         delegate long CalcFolderSizeHandler(string folderName);
 4         static CalcFolderSizeHandler handler = new CalcFolderSizeHandler(CalcFolderSize);
 5         static void Main(string[] args)
 6         {
 7             while (true)
 8             {
 9                 Console.WriteLine("请输入文件夹名称:");
10                 string folderName = Console.ReadLine();
11                 IAsyncResult result = handler.BeginInvoke(folderName, ShowFolderSize, folderName);
12                 Console.ReadKey();
13             }
14         }
15         private static void ShowFolderSize(IAsyncResult result)
16         {
17             try
18             {
19                 long totalSize = handler.EndInvoke(result);
20                 //这里处理没有输入完就就显示结果的问题
21                 while (Console.CursorLeft != 0)
22                     System.Threading.Thread.Sleep(2*1000);
23                 Console.WriteLine(string.Format("文件夹:{0},容量为:{1}", result.AsyncState.ToString(), totalSize));
24             }
25             catch (Exception ex)
26             {
27                 Console.WriteLine(ex.Message);   
28             }
29         }
30         private static long CalcFolderSize(string folderName)
31         {
32             try
33             {
34                 if (!Directory.Exists(folderName))
35                     return 0;
36                 DirectoryInfo rootDir = new DirectoryInfo(folderName);
37                 DirectoryInfo[] childDirs = rootDir.GetDirectories();
38                 FileInfo[] files = rootDir.GetFiles();
39                 long totalSize = 0;
40                 foreach (FileInfo file in files)
41                 {
42                     totalSize += file.Length;
43                 }
44                 foreach (DirectoryInfo dir in childDirs)
45                 {
46                     totalSize += CalcFolderSize(dir.FullName);
47                 }
48                 return totalSize;
49             }
50             catch (Exception ex)
51             {
52                 Console.WriteLine(ex.Message);
53                 return 0;
54             }
55         }
56     }

 5.异步编程小结:

异步编程比较适合在后台运行比较耗费时间的比较简单的任务,但是要注意这些任务彼此应是独立的,而且这些任务中不应该有代码直接访问可视化控件。

如果要在后台完成的工作必须按照特定的顺序运行,或者必须访问共享资源,则这些任务就不适合采用异步编程方式,而直接采用多线程技术开发。

posted on 2014-08-29 16:04  yutianu  阅读(129)  评论(0)    收藏  举报

导航