C# 异步调用 BeginInvoke与EndInvoke方法

1,444 阅读7分钟

这是我参与8月更文挑战的第27天,活动详情查看:8月更文挑战”  

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

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

异步调用通用模版

异步调用是通过委托进行的

// 启动异步调用
 IAsyncResult rtn = 委托变量.BeginInvoke(……); 
 可以在这一部分干其他一些事,程序处于异步执行模式
 // 阻塞并等待异步调用结束
 用于保存方法结果的变量=委托变量.EndInvoke(rtn); 

异步调用的核心

异步调用是通过委托来进行的,定义委托:

private delegate long CalculateFolderSizeDelegate(string folderPath);

通过Reflactor反编译结果如下:

    public sealed class CalculateFolderSizeDelegate: MulticastDelegate  
    {  
        public CalculateFolderSizeDelegate(Object target , intmethodPtr)  
        { …… }  
        public virtual long invoke(string folderPath)  
        { …… }  
        public virtual IAsyncResult BeginInvoke( string folderPath,  AsyncCallbackcallback result, object asyncState)  
        { …… }  
        public virtual long EndInvoke( IAsyncResultresult )  
        { …… }  
    }  

由此我们发现,当我们定义一个委托的时候,实际上是定义了一个委托类型,这个类型有invoke、BeginInvoke()、EndInvoke()几个成员方法,而这几个成员方法可以实现一步调用机制。我们看看这几个方法格式怎么定义的 

BeginInvoke方法用于启动异步调用

BeginInvoke()的函数声明:

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

函数返回值类型:

    public interface IAsyncResult
    {
        // 摘要:
        //     获取用户定义的对象,它限定或包含关于异步操作的信息。
        //
        // 返回结果:
        //     用户定义的对象,它限定或包含关于异步操作的信息。
        object AsyncState { get; }
        //
        // 摘要:
        //     获取用于等待异步操作完成的 System.Threading.WaitHandle。
        //
        // 返回结果:
        //     用于等待异步操作完成的 System.Threading.WaitHandle。
        WaitHandle AsyncWaitHandle { get; }
        //
        // 摘要:
        //     获取一个值,该值指示异步操作是否同步完成。
        //
        // 返回结果:
        //     如果异步操作同步完成,则为 true;否则为 false。
        bool CompletedSynchronously { get; }
        //
        // 摘要:
        //     获取一个值,该值指示异步操作是否已完成。
        //
        // 返回结果:
        //     如果操作完成则为 true,否则为 false。
        bool IsCompleted { get; }
    }
  1. BeginInvoke返回IAsyncResult,可用于监视调用进度。
  2. IAsyncResult是从开始操作返回的,并且可用于获取有关异步操作是否已完成的状态。
  3. 结果对象被传递到结束操作,该结束操作返回调用的最终返回值。
  4. 在开始操作中可以提供可选的回调。如果提供回调,在调用结束后,将调用该回调;并且回调中的代码可以调用结束操作。
  5. 如果需要将一些额外的信息传送给回调函数,就将其放入BeginInvoke()方法的第3个参数asyncState中。注意到这个参数的类型为Object,所以可以放置任意类型的数据。如果有多个信息需要传送给回调函数,可以将所有要传送的信息封状到一个Struct变量,或者干脆再定义一个类,将信息封装到这个类所创建的对象中,再传送给BeginInvoke()方法。

EndInvoke方法用于检索异步调用结果

方法声明:

public <方法返回值类型>EndInvoke(<声明为refout的参数>, IAsyncResult result )
  1. result参数由BeginInvoke()方法传回,.NET借此以了解方法调用是否完成。\
  2. 当EndInvoke方法发现异步调用完成时,它取出此异步调用方法的返回值作为其返回值,如果异步调用方法有声明为ref和out的参数,它也负责填充它。\
  3. 在调用BeginInvoke后可随时调用EndInvoke方法,注意:始终在异步调用完成后调用EndInvoke.如果异步调用未完成,EndInvoke将一直阻塞到异步调用完成。\
  4. EndInvoke的参数包括需要异步执行的方法的out和ref参数以及由BeginInvoke返回的IAsyncResult。

异步调用的实例

以计算文件夹大小为例

计算文件夹大小的API

/// <summary>
/// 计算文件夹的大小
/// </summary>
/// <param name="folderPath">文件夹的全路径</param>
/// <returns>返回文件夹的大小</returns>
private static long CalculateFolderSize(string folderPath)
{
    if (!Directory.Exists(folderPath))
    {
        throw new DirectoryNotFoundException("文件夹路径不存在");
    }

    long          totalSize = 0;
    DirectoryInfo rootDir = new DirectoryInfo(folderPath);

    // 获取所有的子文件夹  
    DirectoryInfo[] childDirs = rootDir.GetDirectories();

    // 获取当前文件夹中的所有文件  
    FileInfo[] files = rootDir.GetFiles();

    // 累加每个文件的大小  
    foreach (FileInfo file in files)
    {
        totalSize += file.Length;
    }

    // 对每个文件夹执行同样的计算过程,累加其下所有文件的大小  
    // 通过递归调用实现的  
    foreach (DirectoryInfo dir in childDirs)
    {
        totalSize += CalculateFolderSize(dir.FullName);
    }

    return(totalSize);
}

计算文件夹大小的委托定义

private delegate long CalculateFolderSizeDelegate(string folderPath);

实例化委托调用

public FileOper()
{
    _invoke  = new CalculateFolderSizeDelegate(CalculateFolderSize);
}

// 开始回调
public void BeginInvoke(string folderPath)
{
    _invoke.BeginInvoke(folderPath, ShowFolderSize, folderPath);
}

定义委托回调函数

//1.0 用于回调的函数
//2.0 IAsyncResult是BeginInvoke执行之后传递给它的返回值。
//    BeginInvoke是在一开始操作就返回的,返回的值是IAsyncResult类型,用于监视异步调用的状态,
//    所以它会自动把这个返回值传递给回调函数。
//3.0 回调函数中的IAsyncResult.AsyncState对应着BeginInvoke的第三个参数,即附加信息。
//4.0 使用IAsyncResult的IsCompleted属性可以判断异步调用是否完成。
//5.0 使用IAsyncResult的AsyncWaitHandle.WaitOne可以等待异步调用完成,
//    WaitOne的第一个参数表示要等待的毫秒数,当等待指定时间之后,异步调用仍未完成,WaitOne方法返回false,
//    如果指定时间为0,表示不等待,如果为-1,表示永远等待,直到异步调用完成。
private void ShowFolderSize(IAsyncResult result)
{
    try
    {
        long size = _invoke.EndInvoke(result);
        Console.WriteLine("\n文件夹{0}的容量为: {1}字节\n", (string)(result.AsyncState), size);
    }
    catch (DirectoryNotFoundException e)
    {
        Console.WriteLine("文件夹路径不存在");
    }
}  

测试函数

string _folderName = @"D:\Software";
FileOper _fileOper = new FileOper();

while (true)
{
    _fileOper.BeginInvoke(_folderName);
}  

测试结果

核心cs文件

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

namespace InvokeConsole
{
    //1.0 异步调用是通过委托进行的
    //2.0 异步调用的通用模版:
    // IAsyncResult rtn = 委托变量.BeginInvoke(……); // 启动异步调用
    // 可以在这一部分干其他一些事,程序处于异步执行模式
    // 用于保存方法结果的变量=委托变量.EndInvoke(rtn); // 阻塞并等待异步调用结束
    /// <summary>
    /// 文件操作
    /// </summary>
    class FileOper
    {
        private delegate long CalculateFolderSizeDelegate(string folderPath);
        private CalculateFolderSizeDelegate _invoke = null;
       
        public FileOper()
        {
            _invoke  = new CalculateFolderSizeDelegate(CalculateFolderSize);
        }

        // 开始回调
        public void BeginInvoke(string folderPath)
        {
            _invoke.BeginInvoke(folderPath, ShowFolderSize, folderPath);
        }

        //1.0 用于回调的函数
        //2.0 IAsyncResult是BeginInvoke执行之后传递给它的返回值。
        //    BeginInvoke是在一开始操作就返回的,返回的值是IAsyncResult类型,用于监视异步调用的状态,
        //    所以它会自动把这个返回值传递给回调函数。
        //3.0 回调函数中的IAsyncResult.AsyncState对应着BeginInvoke的第三个参数,即附加信息。
        //4.0 使用IAsyncResult的IsCompleted属性可以判断异步调用是否完成。
        //5.0 使用IAsyncResult的AsyncWaitHandle.WaitOne可以等待异步调用完成,
        //    WaitOne的第一个参数表示要等待的毫秒数,当等待指定时间之后,异步调用仍未完成,WaitOne方法返回false,
        //    如果指定时间为0,表示不等待,如果为-1,表示永远等待,直到异步调用完成。
        private void ShowFolderSize(IAsyncResult result)
        {
            try
            {
                long size = _invoke.EndInvoke(result);
                Console.WriteLine("\n文件夹{0}的容量为: {1}字节\n", (string)(result.AsyncState), size);
            }
            catch (DirectoryNotFoundException e)
            {
                Console.WriteLine("文件夹路径不存在");
            }
        }  

        /// <summary>
        /// 计算文件夹的大小
        /// </summary>
        /// <param name="folderPath">文件夹的全路径</param>
        /// <returns>返回文件夹的大小</returns>
        private static long CalculateFolderSize(string folderPath)
        {
            if (!Directory.Exists(folderPath))
            {
                throw new DirectoryNotFoundException("文件夹路径不存在");
            }

            long          totalSize = 0;
            DirectoryInfo rootDir = new DirectoryInfo(folderPath);

            // 获取所有的子文件夹  
            DirectoryInfo[] childDirs = rootDir.GetDirectories();

            // 获取当前文件夹中的所有文件  
            FileInfo[] files = rootDir.GetFiles();

            // 累加每个文件的大小  
            foreach (FileInfo file in files)
            {
                totalSize += file.Length;
            }

            // 对每个文件夹执行同样的计算过程,累加其下所有文件的大小  
            // 通过递归调用实现的  
            foreach (DirectoryInfo dir in childDirs)
            {
                totalSize += CalculateFolderSize(dir.FullName);
            }

            return(totalSize);
        }
    }

}