[C#学习笔记之异步编程模式2]BeginInvoke和EndInvoke方法

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

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


异步调用通用模板


//……
//普通的代码:处于同步执行模式
IAsyncResultret=委托变量.BeginInvoke(……);  //启动异步调用
//可以在这一部分干其他一些事,程序处于异步执行模式
用于保存方法结果的变量=委托变量.EndInvoke(ret);  //结束异步调用
//普通的代码:处于同步执行模式
//……

对照上一篇文章中的计算指定文件夹的容量的例子(例2)
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;


namespace AsyncCalculateFolderSize1
{
    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.WriteLine("正在计算中,请耐心等待……");
            //阻塞,等到调用完成,取出结果
            long size = d.EndInvoke(ret);
            
            Console.WriteLine("\n计算完成。文件夹{0}的容量为:{1}字节\n", FolderName, size);
        }
    }
}

异步调用的奥秘

异步调用是通过委托来进行的,我们看看是如何定义委托的:
public delegate long CalculateFolderSizeDelegate(string FolderName);
通过Reflactor反编译结果如下:                                    
public sealed class CalculateFolderSizeDelegate: MulticastDelegate
{
public CalculateFolderSizeDelegate(Object target , intmethodPtr)
{ …… }
public virtual long invoke(string FolderName)
{ …… }
public virtual IAsyncResult BeginInvoke( string FolderName,
AsyncCallbackcallback , object asyncState)
{ …… }
public virtual long EndInvoke( IAsyncResultresult )
{ …… }
}
由此我们发现,当我们定义一个委托的时候,实际上是定义了一个委托类型,这个类型有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.使用轮询等待异步调用完成:使用IAsyncResultIsCompleted属性来判断异步调用是否完成

 虽然上面的方法可以很好地实现异步调用,但是当调用EndInvoke方法获得调用结果时,整个程序就象死了一样,依然要等待异步方法执行结束,这样做用户的感觉并不会太  好,因此,我们可以使用 asyncResult来判断异步调用是否完成,并显示一些提示信息。这样做可以增加用户体验。代码如下:
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);
        }
    }
}
这样,当程序在执行CalculateFolderSize这个异步方法的时候主线程并不是“假死”,而是每隔0.2毫秒输出一个“.",这就是异步调用的妙处!
这里需要用到BeginInvoke的返回值IAsyncResult的IsCompleted这个属性来判断异步线程是否执行结束。

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

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[] args)
        {
            //定义一个委托变量引用静态方法CalculateFolderSize
            CalculateFolderSizeDelegate d = CalculateFolderSize;

            Console.WriteLine("请输入文件夹名称(例如:C:\\Windows):");

            string FolderName = Console.ReadLine();

            //通过委托异步调用静态方法CalculateFolderSize
            IAsyncResult ret = d.BeginInvoke(FolderName, null, null);

            Console.Write("正在计算中,请耐心等待");

            while(!ret.AsyncWaitHandle.WaitOne(2000))
            {
                //等待2秒钟,输出一个“.”
                Console.Write(".");
            }

            //阻塞,等到调用完成,取出结果
            long size = d.EndInvoke(ret);

            Console.WriteLine("\n计算完成。文件夹{0}的容量为:{1}字节\n", FolderName, size);
        }
    }
}

 WaitOne的第一个参数表示要等待的毫秒数,在指定时间之内,WaitOne方法将一直等待,直到异步调用完成,并发出通知,WaitOne方法才返回true。当等待指定时间之后,异步调用仍未完成,WaitOne方法返回false,如果指定时间为0,表示不等待,如果为-1,表示永远等待,直到异步调用完成。

3.使用异步回调函数
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace AsyncCalculateFolderSize4
{
    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);
        private static CalculateFolderSizeDelegate task = CalculateFolderSize;

        //用于回调的函数
        public static void ShowFolderSize(IAsyncResult result)
        {
            long size = task.EndInvoke(result);
            Console.WriteLine("\n文件夹{0}的容量为:{1}字节\n", (String)result.AsyncState, size);
        }

        static void Main(string[] args)
        {
            string FolderName;
            while (true)
            {
                Console.WriteLine("请输入文件夹名称(例如:C:\\Windows),输入quit结束程序");
                FolderName = Console.ReadLine();
                if (FolderName == "quit")
                    break;
                task.BeginInvoke(FolderName, ShowFolderSize, FolderName);//第一个参数是异步函数的参数,第二个参数是回调函数,第三个参数是回调函数的参数,回调函数会在异步函数执行结束之后被调用。
            }
        }
    }
}
这个例子中通过循环的输入文件夹名称计算文件夹容量,计算的操作放在异步调用函数中,因此我们在输入下一个文件夹名称时不必等待上一个计算结束,异步函数执行完成之后会自动调用回调函数ShowFolderSize进行结果处理。
[C#学习笔记之异步编程模式2]BeginInvoke和EndInvoke方法_第1张图片
(今天就学到这里,下午去嘉禾看期待已久的3D《复仇者联盟》,吼吼......)
2012/5/20 23:05补充

对于上面最后一个异步回调的例子有一个缺陷,就是当异步调用的函数与主线程都需要访问同一资源时,要注意解决资源共享的问题。如下图:
[C#学习笔记之异步编程模式2]BeginInvoke和EndInvoke方法_第2张图片
修改程序如下:
using System;
using System.Collections.Generic;
using System.Text;
using System.IO;

namespace AsyncCalculateFolderSize6
{
    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);
        private static CalculateFolderSizeDelegate d = new CalculateFolderSizeDelegate(CalculateFolderSize);

        //用于回调的函数
        public static void ShowFolderSize(IAsyncResult result)
        {
            try
            {
                long size = d.EndInvoke(result);
                while (Console.CursorLeft != 0)//只有用户不输入,且光标位于第一列时,才输出信息。
                {
                    //等待2秒
                    System.Threading.Thread.Sleep(2000);
                }
                Console.WriteLine("\n文件夹{0}的容量为:{1}字节\n", (String)result.AsyncState, size);
            }
            catch (DirectoryNotFoundException e)
            {
                Console.WriteLine("您输入的文件夹不存在");
            }
        }

        static void Main(string[] args)
        {
            string FolderName;
            while (true)
            {
                Console.WriteLine("请输入文件夹名称(例如:C:\\Windows),输入quit结束程序");
                FolderName = Console.ReadLine();
                if (FolderName == "quit")
                    break;
                d.BeginInvoke(FolderName, ShowFolderSize, FolderName);
            }
        }
    }
}


你可能感兴趣的:(编程,windows,String,object,C#,Class)