当前位置: 代码迷 >> C# >> C# 书中疑惑请问(同步、异步)
  详细解决方案

C# 书中疑惑请问(同步、异步)

热度:102   发布时间:2016-05-05 04:27:25.0
C# 书中疑惑请教(同步、异步)
最近找了一本“C#网络编程高级篇之网页游戏辅助程序设计”的书在看,在第二章套接字编程中,书中讲解了同步套接字TCP客户端程序和异步套接字TCP客户端编程(暂时先只理解客户端,服务器端暂时不管),以下是书中同步与异步的程序(疑惑的地方我会注明),我主要疑惑的是在同步中连接服务器后就开一个线程去接收数据,在异步中,是连接服务器后,通过回调自身来实现数据的不断接受,按照对异步的理解,当使用异步回调时相当于另开了一个后台子线程,而主程序可以接着往下执行。但这样我感觉就和同步中开一个接收子线程的就没什么区别了,都是开了子线程去接收数据。。。是否是由于同步中开了一个子线程后是“抢答”式的在主线程、子线程之间切换,而异步就是开了一个后台子线程,是并行的?还是在占用资源上有区别?
同步接收程序:


private void AccepMessage()//接收数据的函数
        {
            NetworkStream netStream = new NetworkStream(socket);
            while (true)
            {
                try
                {
                    byte[] datasize = new byte[4];
                    netStream.Read(datasize, 0, 4);
                    int size = System.BitConverter.ToInt32(datasize, 0);
                    Byte[] message = new byte[size];
                    int dataleft = size;
                    int start = 0;
                    while (dataleft > 0)
                    {
                        int recv = netStream.Read(message, start, dataleft);
                        start += recv;
                        dataleft -= recv;
                    }
                    this.rchTxtBoxReceive.Rtf = System.Text.Encoding.Unicode.GetString(message);
                }
                catch
                {
                    break;
                }
            }
        }

        private void btnRequest_Click(object sender, EventArgs e)
        {
            IPEndPoint server = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 6888);
            socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            try
            {
                socket.Connect(server);
            }
            catch
            {
                MessageBox.Show("与服务器连接失败!");
                return;
            }
            this.btnRequest.Enabled = false;
            this.lstBoxState.Items.Add("与服务器连接成功");
            thread = new Thread(new ThreadStart(AccepMessage));//开启线程,接收数据,就是此处与异步的区别???
            thread.Start();
        }


异步接收程序:

private void ConnectCallback(IAsyncResult ar)
        {
            try
            {
                //Socket client1 = (Socket)ar.AsyncState;
                client.EndConnect(ar);
                lstBoxMessage.Invoke(AppendString, String.Format("已经成功连接到服务器{0}!", client.RemoteEndPoint.ToString()));
                lstBoxMessage.Invoke(AppendString, String.Format("本地端接点为{0}!", client.LocalEndPoint.ToString()));
                Rcvbuffer = new byte[client.SendBufferSize];
             //开始回调接收,但也是相当于开了一个线程,与同步的区别???
                AsyncCallback callback = new AsyncCallback(ReceiveCallback);
                client.BeginReceive(Rcvbuffer, 0, Rcvbuffer.Length, SocketFlags.None, callback, client);
            }
            catch
            {
                lstBoxMessage.Invoke(AppendString, String.Format("无法建立与服务器的连接!"));
            }
        }
        private void ReceiveCallback(IAsyncResult ar)//异步回调接收
        {
            try
            {
                int i = client.EndReceive(ar);
                string data = string.Format("收:{0}", Encoding.UTF8.GetString(Rcvbuffer, 0, i));
                lstBoxMessage.Invoke(AppendString, data);

                Rcvbuffer = new byte[client.SendBufferSize];
                AsyncCallback callback = new AsyncCallback(ReceiveCallback);
                client.BeginReceive(Rcvbuffer, 0, Rcvbuffer.Length, SocketFlags.None, callback, client);
            }
            catch (Exception ex)
            {
                if (client != null)
                {
                    client.Shutdown(SocketShutdown.Both);
                    client.Close();
                    client = null;
                    lstBoxMessage.Invoke(AppendString, ex.Message);
                }
            }
        }      

------解决思路----------------------
同步会阻塞你的主线程,让你的程序假死,异步是让子线程去做事,你还可以干其他的,不影响,子线程干完了,通知你
------解决思路----------------------
Begin-End模式是利用委托交给托管线程池去执行,本质上都是利用子线程去异步执行
不同的是,托管线程池内部作了优化,通常情况都会有空闲的线程直接去跑,要比你手动去开一个线程快一点(当然你是感觉不到的)
Begin-End相对固化,用起来方便,线程资源回收更高效
自己开线程当然更加灵活,要不然就等于捆住手脚了。
大多数情况还是推荐用封装好的,维护起来方便,最起码大大降低了初学者犯错的机率


------解决思路----------------------
引用:
Quote: 引用:

Begin-End模式是利用委托交给托管线程池去执行,本质上都是利用子线程去异步执行
...

哦  这样啊!多谢回答。。。


2 楼dongxinxi朋友的回复不是很准确。
------解决思路----------------------
Socket的异步接收不一定要用到线程。如果异步接收用IOCP来完成,那么并不需要一个线程来等待数据(但一旦有了数据,则需要一个线程来运行回调函数)。具体看如下例子(例子用Socket.Accept,但Socket.Receive道理相同)。
static void Main(string[] args)
{
    bool sync = true;

    for(int i = 0; i < 20; i++)
    {
        int workerThreads, iocpThreads;
        ThreadPool.GetAvailableThreads(out workerThreads, out iocpThreads);
        Console.WriteLine("worker:{0} iocp:{1}", workerThreads, iocpThreads);

        Socket socket = new Socket( AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        socket.Bind(new IPEndPoint(IPAddress.Loopback, 0));
        socket.Listen(3);
        if (sync)
        {
            ThreadPool.QueueUserWorkItem(state => { socket.Accept(); });
        }
        else
        {
            socket.BeginAccept((ir) => { Console.WriteLine("connection accepted"); }, null);
        }
        Thread.Sleep(1000);
    }
}

在我的机器上,当sync=true的时候,运行结果如下(注意每个同步接收占用一个工作线程):

worker:1023 iocp:1000
worker:1022 iocp:1000
worker:1021 iocp:1000
worker:1020 iocp:1000
worker:1019 iocp:1000
worker:1018 iocp:1000
worker:1017 iocp:1000
worker:1016 iocp:1000
...

当sync=false的时候,运行结果如下(注意可用worker线程数量并没有减少)。
worker:1023 iocp:1000
worker:1023 iocp:1000
worker:1023 iocp:1000
worker:1023 iocp:1000
worker:1023 iocp:1000
worker:1023 iocp:1000

------解决思路----------------------
谢谢LS的更正,线程池中的线程分两个工种。之前只是针对LZ的问题对比了下
IAsyncResult接口中有一个CompletedSynchronously属性,可以告诉你是否同步完成,这个还是知道的
  相关解决方案