当前位置: 代码迷 >> C# >> C# 异步回调的困惑
  详细解决方案

C# 异步回调的困惑

热度:58   发布时间:2016-05-05 04:26:52.0
C# 异步回调的疑惑
最近学TCP通讯,用C#写一个TCP通讯的界面,完成数据的接收和发送的,遇到的问题:我使用异步回调的方式来接收数据,也是用异步的方式来实现超时机制,但我不明白的是异步回调时是什么时候将结果返回给主线程的。如下面的超时机制程序,如果阻塞当前线程并等待,直到TimeoutObject.Set被调用才重新执行,那么不管是否连接上TimeoutObject.Set都会被调用,这样就会抛出异常,但不是超时异常。该程序经过试验可以实现功能,我所不明白的是该程序的执行过程,以及异步回调在什么情况下才会返回,如果没有达到返回条件,是否会像while一样一直执行下去???希望对异步比较了解的回答一下,不胜感激!另外,MSDN上说了一些例子,但我对其的执行过程也理解有偏差。。。

class TimeOutSocket
{
    private static bool IsConnectionSuccessful = false;
    private static Exception socketexception;
    private static ManualResetEvent TimeoutObject = new ManualResetEvent(false);

    public static TcpClient TryConnect(IPEndPoint remoteEndPoint, int timeoutMiliSecond)
    {
        TimeoutObject.Reset();
        socketexception = null;  

        string serverip = Convert.ToString(remoteEndPoint.Address);
        int serverport = remoteEndPoint.Port;           
        TcpClient tcpclient = new TcpClient();
        
        tcpclient.BeginConnect(serverip, serverport, 
            new AsyncCallback(CallBackMethod), tcpclient);

        if (TimeoutObject.WaitOne(timeoutMiliSecond, false))
        {
            if (IsConnectionSuccessful)
            {
                return tcpclient;
            }
            else
            {
                throw socketexception;
            }
        }
        else
        {
            tcpclient.Close();
            throw new TimeoutException("TimeOut Exception");
        }
    }
    private static void CallBackMethod(IAsyncResult asyncresult)
    {
        try
        {
            IsConnectionSuccessful = false;
            TcpClient tcpclient = asyncresult.AsyncState as TcpClient;
             
            if (tcpclient.Client != null)
            {
                tcpclient.EndConnect(asyncresult);
                IsConnectionSuccessful = true;
            }
        }
        catch (Exception ex)
        {
            IsConnectionSuccessful = false;
            socketexception = ex;
        }
        finally
        {
            TimeoutObject.Set();
        }
    }
}

------解决思路----------------------
你先学习一下委托和事件,就能大体明白回调函数是怎么个思想了

至于回调函数到底什么时候会被执行,这要看MSDN,不一样的接口当然具体实现是不一样的
------解决思路----------------------
用通俗易懂的话讲,while循环,阻塞式的同步方式,就好比你每隔5分钟下楼看看有没有快递送来
而异步的方式就是你把电话告诉快递公司,他们到了会给你打电话

那么他们什么时候打,打还是不打,就不取决于你了
------解决思路----------------------
所以说你其实还是不懂

快递公司到货了会给你打电话
那么如果他来的路上找不到你家在哪里,也会给你打电话
如果他有事今天突然来不了了,同样会给你打电话

你的问题就好像在问,如果他还在路上,是否是一直在给你打电话??
------解决思路----------------------
回调函数是基于事件的,而事件是用委托实现的
你把"到了给我打电话"这个事件委托给快递公司,快递公司在该给你打电话的时候,就会去执行,他逻辑上是怎么执行的你其实不用操心,但是绝对不是不停的去摸电话那样执行的
------解决思路----------------------
“异步”概念,就不存在什么“等待、死循环、阻塞”的机制。如果你的主线程阻塞,那是你的事情,不关异步。

异步其实就是“注册回调方法”。主线程注册一个方法给另一个对象,如果这个对象使用子线程来工作,那么主线程立刻就继续执行后边的代码了,根本不会去等待着“返回”;而如果另一个方法没有使用子线程去做主要工作,而是还是使用当前主线程继续工作,那么这个回调方法也就是顺序调用的。

你的代码,调用了BeginConnect之后(它将来可能在子线程中回调CallBackMethod方法,也可能永远也不回调),你进行了“阻塞”,那么在逻辑上这其实跟顺序执行并没有什么不一样。因为主线程被阻塞了,主线程不能干别人任何事情而只能“死等”。

也就是在客户端的代码中可以这样写,客户端代码可以任性一些不要求性能。如果是服务器端程序,进程中往往并发有几十个任务需要执行(整个windows往往会并发上千个线程在繁忙地工作)。如果有5000个客户端连接,你使用5000个线程在哪里阻塞着Receive操作,然后再另外使用一个线程在那里阻塞着Connect操作,那么这对服务器系统的性能就是极大地伤害。这类程序就会让系统可撑住的客户数量降低5倍以上。所以使用IOCP的windows服务器端不会去轻易写阻塞代码,即使有5000个客户端保持连接和通讯,可能有平均10个线程在工作。所以不写阻塞、循环代码才是真正的并发多线程程序编程之道。

比如说定时器机制,是优化了的基于底层时钟中断的机制,而且许多定时器对象会自动共用一些资源,绝不是有些初学者以为的“每一个定时器都占用一个线程在那里死循环”。所以定时器并不是滥用线程的。

你的代码如果不写阻塞,那么在BeginConnect执行之前让一个系统定时器开始计时,然后 BeginConnect 语句一瞬间就完成了(注册完毕回调方法),那么你的的主线程就可以结束了,可以去干别的去了。然后当定时器事件触发,你就知道连接超时了;反之如果CallBackMethod回调被执行,在第一条语句就应该停止定时器(作为asyncresult内的属性将定时器的引用传递给它)。这样就不会占用任何线程,没有任何代码在那里“循环、阻塞”着。
------解决思路----------------------
我们再把“异步”的模式从最基础的看一下(先不要结合这么高大上的通讯机制),比如说你要做一个“加法操作”:
private static void Plus(int a, int b, Action<int> callback)
{
    callback(a + b);
}


这样一个代码就是把a+b的值作为callback的参数而回调。你的主程序如果调用它,那么其实也是执行了这个a+b之后才可能继续执行随后的操作。这时候你可能把这个回调方法的输入参数“说成是”加法操作的返回值,因为这其实是用同步调用的方式来实现这个写为异步调用语法的代码的。

但是为什么会有这种异步调用设计模式?因为这通常隐藏了子线程(真正异步)调用的机制。假设这个加法需要到别的机器上去计算(比如说需要跑到5万公里以外的机器上才能计算),需要花时间,那么它可能就是使用子线程来执行它、子线程来回调callback方法,而你主线程一瞬间就执行完毕了,不会等待5秒钟时间。而这个计算也不是因为要故意不断去循环、故意跟你的父线程斗气而不给它返回结果,它是确实需要在未来某个时间才回调给你结果的,而且它可能永远也不回调。

因此看到异步调用的程序,你在父线程程序逻辑设计上就要抛开“循环、阻塞”的想法。有这个想法,你根本设计不出来异步处理程序流程。
------解决思路----------------------
基本上,设计为“回调、事件”机制的程序,都有可能是在子线程中来回调的(而不是在父线程中回调)。有些人以为事件都是在父线程回调,这是不对的,事件完全有可能在某个子线程被触发。

并且许多方法还用 BeginXXX 这样的命名形式来提醒你“确实是在子线程中回调的”。

当你看到回调形式的接口方法设计时,应该首先想到它有可能是真正异步地执行的。即使是它的实现方法“暂时是”同步执行的(在父线程中进行处理和回调,跟子线程无关),你也应该尽量编写一个“同步、异步”调用都兼容的逻辑程序来。

大部分的回调可能都是用来隐藏上述第二种真正异步调用机制的,你不能先入为主地以为是第一种实现机制。在设计上实际上根本没有“达到返回条件”这种说法,你应该习惯于按照真正异步操作的流程去设计程序。
  相关解决方案