您的位置:首页 > 编程学习 > C#

C#中BeginInvoke与EndInvoke

更多 2015/10/11 来源:C#学习浏览量:1389
学习标签: 多线程
本文导读:C#中BeginInvoke方法可以使用线程异步地执行委托所指向的方法。然后通过EndInvoke方法获得方法的返回值(EndInvoke方法的返回值就是被调用方法的返回值),或是确定方法已经被成功调用。当使用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[] 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,表示永远等待,直到异步调用完成。

 

4、使用回调方式返回结果

要想在调用的过程中,程序仍然可以正常做其它的工作,就必须使用异步调用的方式。

 

 
C# 代码   复制

private delegate int MyMethod();
private int method()
{
     Thread.Sleep(10000);
    return 100;
}
private void MethodCompleted(IAsyncResult asyncResult)
{
    if (asyncResult == null) return;
     textBox1.Text = (asyncResult.AsyncState as 
     MyMethod).EndInvoke(asyncResult).ToString();
}

private void button1_Click(object sender, EventArgs e)
{

     MyMethod my = method;
     IAsyncResult asyncResult = my.BeginInvoke(MethodCompleted, my);
}

实例说明

这里使用了BeginInvoke方法的最后两个参数(如果被调用的方法含有参数的话,这些参数将作为BeginInvoke的前面一部分参数,如果没有参数,BeginInvoke就只有两个参数了)。

第一个参数是回调方法委托类型,这个委托只有一个参数,就是IAsyncResult,如MethodCompleted方法所示。当method方法执行完后,系统会自动调用MethodCompleted方法。

BeginInvoke的第二个参数需要向MethodCompleted方法中传递一些值,一般可以传递被调用方法的委托,如上面代码中的my。这个值可以使用IAsyncResult.AsyncState属性获得。

 

四、BeginInvoke与EndInvoke完整实例

 

 
 
C# 代码   复制

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;

namespace ConsoleApplication1
{

    public delegate void AsynComputeCaller(int num, out int result);

    class Factorial
    {
        public int Compute(int num)
        {
            Thread.Sleep(new Random().Next(100,999));
            if (num == 1) return 1;

            return num * this.Compute(num - 1);
        }

        /// <summary>
        /// 异步计算阶乘
        /// </summary>
        /// <param name="num"></param>
        /// <param name="result"></param>
        public void AsynCompute(int num, out int result)
        {
            Console.WriteLine("\\n   AsynCompute...start");

            DateTime startTime = DateTime.Now;
            result = this.Compute(num);
            DateTime endTime = DateTime.Now;

            Console.WriteLine("\\n   AsynCompute...end(Milliseconds Used:{0})",
                endTime.Subtract(startTime).Milliseconds);
        }
    }

    class Program
    {
        int result = 0;

        /// <summary>
        /// 调用BeginInvoke方法启动异步方法,
        /// 进行某些操作,
        /// 然后调用EndInvoke方法来一直阻止请求线程到调用完成。
        /// </summary>
        void Run()
        {
            Factorial f = new Factorial();
            AsynComputeCaller caller = new AsynComputeCaller(f.AsynCompute);
            Console.WriteLine("Run...start");
            IAsyncResult iAsyncResult = caller.BeginInvoke(4, out result, null, null);
            //主线程进行一些操作
            Console.WriteLine("   主线程进行一些操作");
            for (int i = 0; i < 50; i++)
            {
                Thread.Sleep(50);
                Console.Write(".");
            }
            Console.WriteLine();
            //调用EndInvoke来等待异步调用结束,并获得结果
            caller.EndInvoke(out result, iAsyncResult);
            Console.Write("Run...end(result:{0})", result);
        }

        /// <summary>
        /// 调用BeginInvoke方法启动异步方法,
        /// 使用System.IAsyncResult.AsyncWaitHandle属性获取WaitHandle,
        /// 使用它的WaitOne方法一直阻止执行直到发出WaitHandle信号,
        /// 然后调用EndInvoke方法。
        /// </summary>
        void Run2()
        {
            Factorial f = new Factorial();
            AsynComputeCaller caller = new AsynComputeCaller(f.AsynCompute);
            Console.WriteLine("Run...start");
            IAsyncResult iAsyncResult = caller.BeginInvoke(12, out result, null, null);
            //主线程进行一些操作
            Console.WriteLine("   主线程进行一些操作");
            for (int i = 0; i < 50; i++)
            {
                Thread.Sleep(50);
                Console.Write(".");
            }
            Console.WriteLine();
            Console.WriteLine("   等待WaitHandle接收到信号");
            //等待WaitHandle接收到信号
            iAsyncResult.AsyncWaitHandle.WaitOne();
            caller.EndInvoke(out result, iAsyncResult);
            Console.Write("Run...end(result:{0})", result);
        }

        /// <summary>
        /// 调用BeginInvoke方法启动异步方法,
        /// 轮询由BeginInvoke返回的IAsyncResult,
        /// 确定异步调用何时完成,
        /// 然后调用EndInvoke。
        /// </summary>
        void Run3()
        {
            Factorial f = new Factorial();
            AsynComputeCaller caller = new AsynComputeCaller(f.AsynCompute);
            Console.WriteLine("Run...start");
            IAsyncResult iAsyncResult = caller.BeginInvoke(6, out result, null, null);
            //主线程进行一些操作
            Console.WriteLine("   主线程进行一些操作");
            while(!iAsyncResult.IsCompleted)
            {
                Thread.Sleep(100);
                Console.Write(".");
            }
            Console.WriteLine();
            Console.WriteLine("   异步方法已经结束");
            //等待WaitHandle接收到信号
            iAsyncResult.AsyncWaitHandle.WaitOne();
            caller.EndInvoke(out result, iAsyncResult);
            Console.Write("Run...end(result:{0})", result);
        }

        /// <summary>
        /// 调用BeginInvoke方法启动异步方法时,
        /// 将代表异步方法完成时需要回调的方法的委托传递给BeginInvoke。
        /// 异步调用完成后,将在ThreadPool线程上执行该回调方法。
        /// 在该回调方法中调用EndInvoke。
        /// </summary>
        void Run4()
        {
            Factorial f = new Factorial();
            AsynComputeCaller caller = new AsynComputeCaller(f.AsynCompute);
            Console.WriteLine("Run...start");
            IAsyncResult iAsyncResult = caller.BeginInvoke(6, out result,
                new AsyncCallback(CallbackMethod), caller);
            //主线程进行一些操作
            Console.WriteLine("   主线程进行一些操作");
            for (int i = 0; i < 50; i++)
            {
                Thread.Sleep(50);
                Console.Write(".");
            }
            Console.WriteLine();
            

        }

        void CallbackMethod(IAsyncResult ar)
        {
            AsynComputeCaller caller = (AsynComputeCaller)ar.AsyncState;
            caller.EndInvoke(out result, ar);
            Console.Write("Run...end(result:{0})", result);
        }

        static void Main(string[] args)
        {
            Program p = new Program();
            Console.WriteLine("\\n\\n*** Run ***");
            p.Run();

            Console.WriteLine("\\n\\n*** Run2 ***");
            p.Run2();

            Console.WriteLine("\\n\\n*** Run3 ***");
            p.Run3();

            Console.WriteLine("\\n\\n*** Run4 ***");
            p.Run4();
            Console.Read();
        }
    }
}
 
 
五、BeginInvoke与Invoke的区别
 
 
1、Invoke() 调用时,会阻塞当前线程,等到 Invoke() 方法返回才继续执行后面的代码,表现出“同步”的概念。
 
2、BeginInvoke() 调用时,当前线程会启用线程池中的某个线程来执行此方法,当前线程不被阻塞,继续运行后面的代码,表现出“异步”的概念。
 
3、EndInvoke() ,在想获取 BeginInvoke() 执行完毕后的结果时,调用此方法来获取。
 
收藏
320
很赞
199