C#中BeginInvoke与EndInvoke

C#中BeginInvoke与EndInvoke

普通方法运行,是单线程的,如果中途有大型操作(如:读取大文件,大批量操作数据库,网络传输等),都会导致方法阻塞,表现在界面上就是,程序卡或者死掉,界面元素不动了,不响应了。

异步方法很好的解决了这些问题,异步执行某个方法,程序立即开辟一个新线程去运行你的方法,主线程包括界面就不会死掉了。

异步调用并不是要减少线程的开销, 它的主要目的是让调用方法的主线程不需要同步等待在这个函数调用上, 从而可以让主线程继续执行它下面的代码.

在C#中使用线程的方法很多,使用委托的BeginInvoke和EndInvoke方法就是其中之一。BeginInvoke方法可以使用线程异步地执行委托所指向的方法。然后通过EndInvoke方法获得方法的返回值(EndInvoke方法的返回值就是被调用方法的返回值),或是确定方法已经被成功调用。

一、BeginInvoke方法

用于启动异步调用

1、定义

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

2、函数返回值类型

  • 
    public interface IAsyncResult
    
    {
    
            object AsyncState{ get;}  //如果有回调函数的话该参数用于保存要传递给回调函数的参数值
    
             WaitHandle AsyncWaitHandle{ get;}
    
            bool CompletedSynchronously{ get;}
    
            bool IsCompleted{ get;} //保存方法是否执行结束,我们可以通过该属性的值来判断异步方法是否执行结束
    
    }
    
    		
  • 3、说明

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

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

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

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

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

    二、EndInvoke方法

    用于检索异步调用结果

    1、定义

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

    2、说明

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

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

    (3).在调用BeginInvoke后可随时调用EndInvoke方法,注意:始终在异步调用完成后调用EndInvoke。

    (4).如果异步调用未完成,EndInvoke将一直阻塞到异步调用完成。

    (5).EndInvoke的参数包括需要异步执行的方法的out和ref参数以及由BeginInvoke返回的IAsyncResult。  

    三、BeginInvoke与EndInvoke实例

    1、直接使用EndInvoke方法来获得返回值

  • 当使用BeginInvoke异步调用方法时,如果方法未执行完,EndInvoke方法就会一直阻塞,直到被调用的方法执行完毕。如下面的代码所示:
  •  
  •  
  • C# 代码   复制
  • 
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading;
    
    namespace ConsoleApplication1
    {
        class Program
        {
            private static int newTask(int ms)
            {
                Console.WriteLine("任务开始");
                Thread.Sleep(ms);
                Random random = new Random();
                int n = random.Next(10000);
                Console.WriteLine("任务完成");
                return n;
            }
    
            private delegate int NewTaskDelegate(int ms);
    
            static void Main(string[] args)
            {
                NewTaskDelegate task = newTask;
                IAsyncResult asyncResult = task.BeginInvoke(2000, null, null);
    
                // EndInvoke方法将被阻塞2秒
                int result = task.EndInvoke(asyncResult);
                Console.WriteLine(result);
    
            }
    
     
        }
    }
    
    			
  • 实例说明

    (1)、在运行上面的程序后,由于newTask方法通过Sleep延迟了2秒,因此,程序直到2秒后才输出最终结果(一个随机整数)。

    (2)、如果不调用EndInvoke方法,程序会立即退出,这是由于使用BeginInvoke创建的线程都是后台线程,这种线程一但所有的前台线程都退出后(其中主线程就是一个前台线程),不管后台线程是否执行完毕,都会结束线程,并退出程序。

     

    2、使用轮询等待异步调用完成:使用IAsyncResultIsCompleted属性来判断异步调用是否完成

    当调用EndInvoke方法获得调用结果时,整个程序就象死了一样,依然要等待异步方法执行结束,这样做用户的感觉并不会太好,因此,我们可以使用 asyncResult来判断异步调用是否完成,并显示一些提示信息。这样做可以增加用户体验。

  •  
  •  
  • C# 代码   复制
  • 
    using System;
    using System.Collections.Generic;
    using System.Text;
    using System.IO;
    
    namespace AsyncCalculateFolderSize2
    {
        class Program
        {
            //计算指定文件夹的总容量
            private static long CalculateFolderSize(string FolderName)
            {
                if (Directory.Exists(FolderName) == false)
                {
                    throw new DirectoryNotFoundException("文件夹不存在");
                }
                DirectoryInfo RootDir = new DirectoryInfo(FolderName);
                //获取所有的子文件夹
                DirectoryInfo[] ChildDirs = RootDir.GetDirectories();
                //获取当前文件夹中的所有文件
                FileInfo[] files = RootDir.GetFiles();
                long totalSize = 0;
                //累加每个文件的大小
                foreach (FileInfo file in files)
                {
                    totalSize += file.Length;
                }
                //对每个文件夹执行同样的计算过程:累加其下每个文件的大小
                //这是通过递归调用实现的
                foreach (DirectoryInfo dir in ChildDirs)
                {
                    totalSize += CalculateFolderSize(dir.FullName);
                }
                //返回文件夹的总容量
                return totalSize;        
            }
    
            //定义一个委托
            public delegate long CalculateFolderSizeDelegate(string FolderName);
    
            static void Main(string[] args)
            {
                //定义一个委托变量引用静态方法CalculateFolderSize
                CalculateFolderSizeDelegate d = CalculateFolderSize;
                Console.WriteLine("请输入文件夹名称(例如:C:\\\\Windows):");
                string FolderName = Console.ReadLine();
    
                //通过委托异步调用静态方法CalculateFolderSize
                IAsyncResult ret = d.BeginInvoke(FolderName, null, null);
    
                Console.Write ("正在计算中,请耐心等待");
    
                //每隔2秒检查一次,输出一个“."
                while (ret.IsCompleted == false)
                {
                    Console.Write(".");
                    System.Threading.Thread.Sleep(200);
                }
    
                //阻塞,等到调用完成,取出结果
                long size = d.EndInvoke(ret);
                Console.WriteLine("\\n计算完成!\\n文件夹{0}的容量为:{1}字节", FolderName, size);
            }
        }
    }
    
    			
  •  

    3、使用轮询等待异步调用完成:使用IAsyncResultAsyncWaitHandle.WaitOne

     

  •  
  • C# 代码   复制
  • 
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.IO;
    
    namespace AsyncCalculateFolderSize3
    {
        class Program
        {
            //计算指定文件夹的总容量
            private static long CalculateFolderSize(string FolderName)
            {
                if (Directory.Exists(FolderName) == false)
                {
                    throw new DirectoryNotFoundException("文件夹不存在");
                }
    
                DirectoryInfo RootDir = new DirectoryInfo(FolderName);
                //获取所有的子文件夹
                DirectoryInfo[] ChildDirs = RootDir.GetDirectories();
                //获取当前文件夹中的所有文件
                FileInfo[] files = RootDir.GetFiles();
                long totalSize = 0;
                //累加每个文件的大小
                foreach (FileInfo file in files)
                {
                    totalSize += file.Length;
                }
                //对每个文件夹执行同样的计算过程:累加其下每个文件的大小
                //这是通过递归调用实现的
                foreach (DirectoryInfo dir in ChildDirs)
                {
                    totalSize += CalculateFolderSize(dir.FullName);
                }
                //返回文件夹的总容量
                return totalSize;
    
            }
    
            //定义一个委托
            public delegate long CalculateFolderSizeDelegate(string FolderName);
    
            static void Main(string标签: