C#异步方法和同步方法的差异浅谈

本文将主要通过“同步调用”、“异步调用”、“异步回调”三个示例来讲解在用委托执行同一个“加法类”的时候的的区别和利弊。

public delegate int AddHandler(int a, int b);
public class 加法类
{
    public static int Add(int a, int b)
    {
        Console.WriteLine("开始计算:" + a + "+" + b);
        Thread.Sleep(3000); //模拟该方法运行三秒
        Console.WriteLine("计算完成!");
        return a + b;
    }
}

同步调用

委托的Invoke方法用来进行同步调用。同步调用也可以叫阻塞调用,它将阻塞当前线程,然后执行调用,调用完毕后再继续向下进行。

public class 同步调用
{
    static void Main()
    {
        Console.WriteLine("===== 同步调用 SyncInvokeTest =====");
        AddHandler handler = new AddHandler(加法类.Add);
        int result = handler.Invoke(1, 2);

        Console.WriteLine("继续做别的事情。。。");

        Console.WriteLine(result);
        Console.ReadKey();
    }
}

异步调用

异步调用不阻塞线程,而是把调用塞到线程池中,程序主线程或UI线程可以继续执行。
委托的异步调用通过BeginInvoke和EndInvoke来实现。

public class 异步调用
{
    static void Main()
    {
        Console.WriteLine("===== 异步调用 AsyncInvokeTest =====");
        AddHandler handler = new AddHandler(加法类.Add);

        //IAsyncResult: 异步操作接口(interface)
        //BeginInvoke: 委托(delegate)的一个异步方法的开始
        IAsyncResult result = handler.BeginInvoke(1, 2, null, null);

        Console.WriteLine("继续做别的事情。。。");

        //异步操作返回
        Console.WriteLine(handler.EndInvoke(result));
        Console.ReadKey();
    }
}

当主线程运行到EndInvoke时,如果这时调用没有结束(这种情况很可能出现),这时为了等待调用结果,线程依旧会被阻塞。
异步委托,也可以参考如下写法:

Action<object> action=(obj)=>method(obj);
action.BeginInvoke(obj,ar=>action.EndInvoke(ar),null);

异步回调

用回调函数,当调用结束时会自动调用回调函数,解决了为等待调用结果让线程依旧被阻塞的局面。

public class 异步回调
{
    static void Main()
    {
        Console.WriteLine("===== 异步回调 AsyncInvokeTest =====");
        AddHandler handler = new AddHandler(加法类.Add);

        //异步操作接口(注意BeginInvoke方法的不同!)
        IAsyncResult result = handler.BeginInvoke(1, 2, new AsyncCallback(回调函数), "AsycState:OK");

        Console.WriteLine("继续做别的事情。。。");
        Console.ReadKey();
    }

    static void 回调函数(IAsyncResult result)
    { 
    //result 是“加法类.Add()方法”的返回值
        //AsyncResult 是IAsyncResult接口的一个实现类,空间:System.Runtime.Remoting.Messaging
        //AsyncDelegate 属性可以强制转换为用户定义的委托的实际类。
        AddHandler handler = (AddHandler)((AsyncResult)result).AsyncDelegate;
        Console.WriteLine(handler.EndInvoke(result));
        Console.WriteLine(result.AsyncState);
    }
}

我定义的委托的类型为AddHandler,则为了访问 AddHandler.EndInvoke,必须将异步委托强制转换为 AddHandler。可以在异步回调函数(类型为 AsyncCallback)中调用 MAddHandler.EndInvoke,以获取最初提交的 AddHandler.BeginInvoke 的结果。 

问题:

(1)int result = handler.Invoke(1,2);
为什么Invoke的参数和返回值和AddHandler委托是一样的呢?
答:
Invoke方法的参数很简单,一个委托,一个参数表(可选),而Invoke方法的主要功能就是帮助你在UI线程上调用委托所指定的方法。Invoke方法首先检查发出调用的线程(即当前线程)是不是UI线程,如果是,直接执行委托指向的方法,如果不是,它将切换到UI线程,然后执行委托指向的方法。不管当前线程是不是UI线程,Invoke都阻塞直到委托指向的方法执行完毕,然后切换回发出调用的线程(如果需要的话),返回。
(2)IAsyncResult result = handler.BeginInvoke(1,2,null,null);
BeginInvoke : 开始一个异步的请求,调用线程池中一个线程来执行,返回IAsyncResult 对象(异步的核心). IAsyncResult 简单的说,他存储异步操作的状态信息的一个接口,也可以用他来结束当前异步。
注意: BeginInvoke和EndInvoke必须成对调用.即使不需要返回值,但EndInvoke还是必须调用,否则可能会造成内存泄漏。

(3)IAsyncResult.AsyncState 属性:
获取用户定义的对象,它限定或包含关于异步操作的信息。 

异步编程概览

.NET Framework 允许您异步调用任何方法。定义与您需要调用的方法具有相同签名的委托;公共语言运行库将自动为该委托定义具有适当签名的 BeginInvoke 和 EndInvoke 方法。

BeginInvoke 方法用于启动异步调用。它与您需要异步执行的方法具有相同的参数,只不过还有两个额外的参数(将在稍后描述)。

BeginInvoke 立即返回,不等待异步调用完成。

BeginInvoke 返回 IasyncResult,可用于监视调用进度。

EndInvoke 方法用于检索异步调用结果。调用BeginInvoke后可随时调用EndInvoke方法;如果异步调用未完成,EndInvoke 将一直阻塞到异步调用完成。EndInvoke的参数包括您需要异步执行的方法的out和ref参数以及由BeginInvoke 返回的IAsyncResult。

四种C#异步方法使用 BeginInvoke 和 EndInvoke 进行异步调用。调用了BeginInvoke后,可以:

1.进行某些操作,然后调用 EndInvoke 一直阻塞到调用完成。

2.使用 IAsyncResult.AsyncWaitHandle 获取 WaitHandle,使用它的 WaitOne 方法将执行一直阻塞到发出 WaitHandle 信号,然后调用EndInvoke。这里主要是主程序等待异步方法,等待异步方法的结果。

3.轮询由 BeginInvoke 返回的 IAsyncResult,IAsyncResult.IsCompeted确定异步调用何时完成,然后调用 EndInvoke。此处理个人认为与相同。

4.将用于回调方法的委托传递给 BeginInvoke。该方法在异步调用完成后在 ThreadPool 线程上执行,它可以调用 EndInvoke。这是在强制装换回调函数里面IAsyncResult.AsyncState(BeginInvoke方法的最后一个参数)成委托,然后用委托执行EndInvoke。

警告   始终在异步调用完成后调用 EndInvoke。

以上有不理解的稍后可以再理解。

C#异步方法例子

1)先来个简单的没有回调函数的异步方法例子

请再运行程序的时候,仔细看注释,对理解很有帮助。还有,若将注释的中的两个方法都同步,你会发现异步运行的速度优越性。

using System;
namespace ConsoleApplication1
{
    class Class1
    {
        //声明委托  
        public delegate void AsyncEventHandler();

        //C#异步方法  
        void Event1()
        {
            Console.WriteLine("Event1 Start");
            System.Threading.Thread.Sleep(4000);
            Console.WriteLine("Event1 End");
        }
        // C#同步方法  
        void Event2()
        {
            Console.WriteLine("Event2 Start");
            int i = 1;
            while (i < 1000)
            {
                i = i + 1;
                Console.WriteLine("Event2 " + i.ToString());
            }
            Console.WriteLine("Event2 End");
        }
        [STAThread]
        static void Main(string[] args)
        {
            long start = 0;
            long end = 0;
            Class1 c = new Class1();
            Console.WriteLine("ready");
            start = DateTime.Now.Ticks;

            //实例委托  
            AsyncEventHandler asy = new AsyncEventHandler(c.Event1);
            //C#异步方法:异步调用开始,没有回调函数和AsyncState,都为null  
            IAsyncResult ia = asy.BeginInvoke(null, null);
            //C#同步方法:同步开始,  
            c.Event2();
            //异步结束,若没有结束,一直阻塞到调用完成,在此返回该函数的return,若有返回值。   
            asy.EndInvoke(ia);

            //都同步的情况。  
            //c.Event1();  
            //c.Event2();  

            end = DateTime.Now.Ticks;
            Console.WriteLine("时间刻度差=" + Convert.ToString(end - start));
            Console.ReadLine();
        }
    }
}

2)下面看有回调函数的WebRequest和WebResponse的异步操作。

在这里有回调函数,且异步回调中又有异步操作。

首先是异步获得ResponseStream,然后异步读取数据。

这个程序非常经典。从中可以学到很多东西的。我们来共同探讨。

using System;
using System.Net;
using System.Threading;
using System.Text;
using System.IO;
// RequestState 类用于通过  
// 异步调用传递数据  
public class RequestState
{
    const int BUFFER_SIZE = 1024;
    public StringBuilder RequestData;
    public byte[] BufferRead;
    public HttpWebRequest Request;
    public Stream ResponseStream;
    // 创建适当编码类型的解码器  
    public Decoder StreamDecode = Encoding.UTF8.GetDecoder();

    public RequestState()
    {
        BufferRead = new byte[BUFFER_SIZE];
        RequestData = new StringBuilder("");
        Request = null;
        ResponseStream = null;
    }
}
// ClientGetAsync 发出异步请求  
class ClientGetAsync
{
    public static ManualResetEvent allDone =
    new ManualResetEvent(false);
    const int BUFFER_SIZE = 1024;

    public static void Main(string[] args)
    {
        if (args.Length < 1)
        {
            showusage();
            return;
        }
        // 从命令行获取 URI  
        Uri HttpSite = new Uri(args[0]);

        // 创建请求对象  
        HttpWebRequest wreq = (HttpWebRequest)WebRequest.Create(HttpSite);

        // 创建状态对象  
        RequestState rs = new RequestState();

        // 将请求添加到状态,以便它可以被来回传递  
        rs.Request = wreq;

        // 发出异步请求  
        IAsyncResult r = (IAsyncResult)wreq.BeginGetResponse(
        new AsyncCallback(RespCallback), rs);

        // 将 ManualResetEvent 设置为 Wait,  
        // 以便在调用回调前,应用程序不退出  
        allDone.WaitOne();
    }
    public static void showusage()
    {
        Console.WriteLine("尝试获取 (GET) 一个 URL");
        Console.WriteLine("\r\n用法::");
        Console.WriteLine("ClientGetAsync URL");
        Console.WriteLine("示例::");
        Console.WriteLine("ClientGetAsync http://www.microsoft.com/net/");
    }

    private static void RespCallback(IAsyncResult ar)
    {
        // 从异步结果获取 RequestState 对象  
        RequestState rs = (RequestState)ar.AsyncState;

        // 从 RequestState 获取 HttpWebRequest  
        HttpWebRequest req = rs.Request;

        // 调用 EndGetResponse 生成 HttpWebResponse 对象  
        // 该对象来自上面发出的请求  
        HttpWebResponse resp = (HttpWebResponse)req.EndGetResponse(ar);

        // 既然我们拥有了响应,就该从  
        // 响应流开始读取数据了  
        Stream ResponseStream = resp.GetResponseStream();

        // 该读取操作也使用异步完成,所以我们  
        // 将要以 RequestState 存储流  
        rs.ResponseStream = ResponseStream;

        // 请注意,rs.BufferRead 被传入到 BeginRead。  
        // 这是数据将被读入的位置。  
        IAsyncResult iarRead = ResponseStream.BeginRead(
        rs.BufferRead, 0, BUFFER_SIZE,
        new AsyncCallback(ReadCallBack), rs);
    }
    private static void ReadCallBack(IAsyncResult asyncResult)
    {
        // 从 asyncresult 获取 RequestState 对象  
        RequestState rs = (RequestState)asyncResult.AsyncState;

        // 取出在 RespCallback 中设置的 ResponseStream  
        Stream responseStream = rs.ResponseStream;

        // 此时 rs.BufferRead 中应该有一些数据。  
        // 读取操作将告诉我们那里是否有数据  
        int read = responseStream.EndRead(asyncResult);

        if (read > 0)
        {
            // 准备 Char 数组缓冲区,用于向 Unicode 转换  
            Char[] charBuffer = new Char[BUFFER_SIZE];

            // 将字节流转换为 Char 数组,然后转换为字符串  
            // len 显示多少字符被转换为 Unicode  
            int len = rs.StreamDecode.GetChars(
            rs.BufferRead, 0, read, charBuffer, 0);
            String str = new String(charBuffer, 0, len);

            // 将最近读取的数据追加到 RequestData stringbuilder 对象中,  
            // 该对象包含在 RequestState 中  
            rs.RequestData.Append(str);


            // 现在发出另一个异步调用,读取更多的数据  
            // 请注意,将不断调用此过程,直到  
            // responseStream.EndRead 返回 -1  
            IAsyncResult ar = responseStream.BeginRead(
            rs.BufferRead, 0, BUFFER_SIZE,
            new AsyncCallback(ReadCallBack), rs);
        }
        else
        {
            if (rs.RequestData.Length > 1)
            {
                // 所有数据都已被读取,因此将其显示到控制台  
                string strContent;
                strContent = rs.RequestData.ToString();
                Console.WriteLine(strContent);
            }

            // 关闭响应流  
            responseStream.Close();

            // 设置 ManualResetEvent,以便主线程可以退出  
            allDone.Set();
        }
        return;
    }
}

C#异步方法和同步方法的差异总结

上面说过,.net framework 可以异步调用任何方法。所以异步用处广泛。

在.net framework 类库中也有很多异步调用的方法。一般都是已Begin开头End结尾构成一对,异步委托方法,外加两个回调函数和AsyncState参数,组成异步操作的宏观体现。所以要做异步编程,不要忘了委托delegate、Begin,End,AsyncCallBack委托,AsyncState实例(在回调函数中通过IAsyncResult.AsyncState来强制转换),IAsycResult(监控异步),就足以理解异步真谛了。

猜你喜欢

转载自blog.csdn.net/f110300641/article/details/88408124