TcpClient.NetworkStream异步操作 - 取消/断开

时间:2017-11-14 17:16:46

标签: c# asynchronous task tcpclient

免责声明:我的C#甚至不如我的C ++那么好。

我正在尝试学习如何在C#中执行异步套接字,以便为我的组件编写测试应用程序。我读了很多方法,但最现代的似乎是使用TcpClient,从中获取NetworkStream,并在返回Task的NetworkStream上调用异步方法。

所以,我开始冒险。

目前尚不清楚如何正确取消异步方法或是否需要。我想在断开连接时手动取消它们吗?有人会认为TcpClient.Close会以一些优雅的方式为我处理。

如果我确实使用手动取消,那么似乎需要一些机制来等待所有异步方法在主线程继续之前退出。在Disconnect()方法的列表中注明。结果是主线程在MakeRequest()或Read()退出之前继续处理和退出,因此有关使用已处置对象的例外情况。

在没有手动取消的测试中,我从异步读取中获得了与使用已经处置的对象相同的异常,因为在退出这些方法之前主线程仍在继续处置和退出。

我想我可以放入自己的同步机制......但是这样做的正确方法是什么?我应该取消吗?是否有一些内置的方法来等待这些方法退出?他们希望我们做什么?

这是我最新的代码尝试:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Sockets;
using System.Text;
using System.Threading.Tasks;

using log4net;
using System.IO;
using System.Threading;

namespace IntegrationTests
{
    public class Client
    {
        private static readonly ILog log = LogManager.GetLogger("root");

        static private ulong m_lastId = 1;

        private ulong m_id;
        private string m_host;
        private uint m_port;
        private uint m_timeoutMilliseconds;
        private string m_clientId;
        private TcpClient m_tcpClient;
        private CancellationTokenSource m_cancelationSource;

        public Client(string host, uint port, string clientId, uint timeoutMilliseconds)
        {
            m_id = m_lastId++;
            m_host = host;
            m_port = port;
            m_clientId = clientId;
            m_timeoutMilliseconds = timeoutMilliseconds;
            m_tcpClient = null;
            m_cancelationSource = null;
        }

        ~Client()
        {
            Disconnect();
        }

        /// <summary>
        /// Attempts to connect to the hostname and port specified in the constructor
        /// </summary>
        /// <throws cref="System.ApplicationException" on failure
        public void Connect()
        {
            Disconnect();

            m_tcpClient = new TcpClient();
            m_cancelationSource = new CancellationTokenSource();

            try
            {
                m_tcpClient.Connect(m_host, (int)m_port);
            }
            catch (SocketException e)
            {
                string msg = string.Format("Client #{0} failed to connect to {1} on port {2}"
                                          , m_id, m_host, m_port);
                throw new System.ApplicationException(msg, e);
            }

            if (m_tcpClient.Connected)
            {
                log.Debug(string.Format("Client #{0} connnected to the Component on {1}"
                                      , m_id, m_tcpClient.Client.RemoteEndPoint.ToString()));
            }
        }

        public void Disconnect()
        {
            if (m_cancelationSource != null)
            {
                m_cancelationSource.Cancel();

                // TODO - There needs to be some kind of wait here until the async methods all return!
                //        How to do that?
                //        Are we even supposed to be manually canceling? One would think TcpClient.Close takes care of that,
                //        however when deleting all cancelation stuff, instead we get exceptions from the async methods about
                //        using TcpClient's members after it was disposed.

                m_cancelationSource.Dispose();
                m_cancelationSource = null;
            }

            if (m_tcpClient != null)
            {
                m_tcpClient.Close();
                m_tcpClient = null;
            }
        }

        public void Login()
        {
            string loginRequest = string.Format("loginstuff{0}", m_clientId);
            var data = Encoding.ASCII.GetBytes(loginRequest);

            NetworkStream stream = m_tcpClient.GetStream();
            Task writeTask = stream.WriteAsync(data, 0, data.Count());

            // This will block until the login is sent
            // We want block until the login is sent, so we can be sure we logged in before making requests
            if( !writeTask.Wait((int)m_timeoutMilliseconds) )
            {
                // Error - Send timed out
                log.Error(string.Format("Client #{0} Timed out while sending login request to the Component"
                                      , m_id));
            }
            else
            {
                log.Debug(string.Format("Client #{0} sent login request to the Component"
                                       , m_id));
            }
        }

        public async void Read()
        {
            byte[] buffer = new byte[1024];
            MemoryStream memoryStream = new MemoryStream();

            NetworkStream networkStream = m_tcpClient.GetStream();
            Task<int> readTask = null;

            bool disconnected = false;

            try
            {
                while (!disconnected)
                {
                    readTask = networkStream.ReadAsync(buffer, 0, buffer.Length, m_cancelationSource.Token);
                    int bytesReceived = await readTask;

                    if (readTask.Status == TaskStatus.RanToCompletion)
                    {
                        if( bytesReceived <= 0)
                        {
                            disconnected = true;
                            continue;
                        }

                        memoryStream.Write(buffer, 0, bytesReceived);

                        // TODO - Handle parsing of messages in the memory stream

                        memoryStream.Seek(0, SeekOrigin.Begin);
                    }
                    else if (readTask.Status == TaskStatus.Canceled)
                    {
                        // Error - Read was cancelled
                        log.Error(string.Format("Client #{0} Read operation was canceled."
                                              , m_id));
                        disconnected = true;
                        continue;
                    }
                    else
                    {
                        // Error - Unexpected status
                        log.Error(string.Format("Client #{0} Read operation has unexpected status after returning from await."
                                              , m_id));
                    }
                }
            }
            catch (System.Exception e)
            {
                log.Error(string.Format("Client #{0} Exception caught while reading from socket. Exception: {1}"
                                       , m_id, e.ToString()));
            }
        }

        public async void MakeRequest(string thingy)
        {
            string message = string.Format("requeststuff{0}", thingy);
            var data = Encoding.ASCII.GetBytes(message);

            NetworkStream networkStream = m_tcpClient.GetStream();
            Task writeTask = null;

            try
            {
                writeTask = networkStream.WriteAsync(data, 0, data.Count(), m_cancelationSource.Token);
                await writeTask;

                if (writeTask.Status == TaskStatus.RanToCompletion)
                {
                    log.Debug(string.Format("Client #{0} sent request for thingy {1} to the Component"
                                           , m_id, thingy));
                }
                else if (writeTask.Status == TaskStatus.Canceled)
                {
                    // Error - Write was cancelled
                    log.Error(string.Format("Client #{0} Write operation was canceled while requesting thingy {1} from the Component"
                                          , m_id, thingy));
                }
                else
                {
                    // Error - Unexpected status
                    log.Error(string.Format("Client #{0} Write operation has unexpected status after returning from await, while requesting thingy {1} from the Component"
                                          , m_id, thingy));
                }
            }
            catch (System.Exception e)
            {
                log.Error(string.Format("Client #{0} Exception caught while requesting thingy {1}. Exception: {2}" 
                                       , m_id, thingy, e.ToString()));
            }
        }
    }
}

主:

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

using log4net;
using log4net.Config;

namespace IntegrationTests
{
    class Program
    {
        private static readonly ILog log = LogManager.GetLogger("root");

        static void Main(string[] args)
        {
            try
            {
                XmlConfigurator.Configure();
                log.Info("Starting Component Integration Tests...");

                Client client = new Client("127.0.0.1", 24001, "MyClientId", 60000);
                client.Connect();

                client.Read();
                client.Login();
                client.MakeRequest("Stuff");

                System.Threading.Thread.Sleep(60000);

                client.Disconnect();
            }
            catch (Exception e)
            {
                log.Error(string.Format("Caught an exception in main. Exception: {0}"
                                      , e.ToString()));
            }
        }
    }
}

0 个答案:

没有答案