【问题标题】:Multi-threaded TcpClient send timeout after long open connection多线程 TcpClient 长时间打开连接后发送超时
【发布时间】:2012-06-07 13:08:05
【问题描述】:

在连接打开很长一段时间(几个小时或一夜之间)后,我在多线程应用程序中因发送超时而关闭 TcpClient 时遇到问题。 NetworkStream 被两个线程使用,一个 UI 线程和一个后台线程。 StreamReader 用于读取传入数据,StreamWriter 用于传出数据。 StreamReader 只被一个线程(后台线程)访问,但 StreamWriter 被 UI 线程和后台线程访问。

如果我打开一个连接并连接到远程服务器,我可以立即发送和接收数据而不会出现任何问题。我没有收到任何发送超时,并且数据已正确发送和接收。但是,如果我随后走开几个小时不发送任何数据,然后返回并开始发送数据(如果这有助于使其有意义,这是一个聊天应用程序),则套接字将在发送时超时。在我离开的那段时间里,接收数据完全没有问题。此外,远程服务器轮询活动连接,我的客户端必须对此做出响应,并且由于连接打开了几个小时,它必须正确发送响应。不过,此轮询响应仅在后台线程上发送。我输入的数据是从 UI 线程发送的,这就是超时发生的地方。

我猜这与并发访问有关,但我不知道是什么原因造成的,以及为什么我最初可以毫无问题地从 UI 发送数据,并且只有在空闲几个小时后才会超时。

下面是相关代码。顶部的变量在类中声明。地址和端口是类中的属性。 WriteLine 是应用程序中使用 StreamWriter 发送数据的唯一方法。我锁定了对 StreamWriter.WriteLine 的调用,希望可以纠正任何同步问题。从 ParseMessage 内部的后台线程以及 UI 的其他地方调用 WriteLine。

如果我将 TcpClient.SendTimeout 增加到更大的值,那并不能解决任何问题。套接字超时只需要更长的时间。我不能让后台线程同时读取和写入,因为后台线程在 ReadLine 上阻塞,所以什么都不会被写入。

private TcpClient _connection;
private StreamWriter _output;
private Thread _parsingThread;
private object _outputLock = new object();


public void Connect(string address, int port)
{           
    Address = address;
    Port = port;

    _parsingThread = new Thread(new ThreadStart(Run));
    _parsingThread.IsBackground = true;
    _parsingThread.Start();
}

private void Run()
{
    try
    {
        using (_connection = new TcpClient())
        {
            _connection.Connect(Address, Port);
            _connection.ReceiveTimeout = 180000;
            _connection.SendTimeout = 60000;

            StreamReader input = new StreamReader(_connection.GetStream());
            _output = new StreamWriter(_connection.GetStream());

            string line;
            do
            {
                line = input.ReadLine();

                if (!string.IsNullOrEmpty(line))
                {
                    ParseMessage(line);
                }
            }
            while (line != null);
        }
    }
    catch (Exception ex)
    {
        //not actually catching exception, just compressing example
    }
    finally
    {
       //stuff
    }
}

protected void WriteLine(string line)
{
    lock (_outputLock)
    {
        _output.WriteLine(line);
        _output.Flush();
    }
}

【问题讨论】:

    标签: c# multithreading timeout tcpclient streamwriter


    【解决方案1】:

    NetworkStream 类的阻塞方法(ReadWrite)并非设计用于从多个线程同时使用。来自 MSDN:

    使用WriteRead 方法进行简单的单线程同步阻塞I/O。如果您想使用单独的线程处理 I/O,请考虑使用 BeginWriteEndWrite 方法,或 BeginReadEndRead 方法进行通信。

    我的假设是,当您从 UI 线程调用 WriteLine(内部为 NetworkStream.Write)时,它会阻塞,直到并发的 ReadLine(内部为 NetworkStream.Read)操作在后台线程中完成.如果后者在SendTimeout 内没有这样做,那么Write 将超时。

    要解决您的问题,您应该将实现转换为使用非阻塞方法。但是,作为首先测试这是否真的问题的快速技巧,请尝试在您的 ReadLine 之前引入 DataAvailable 投票:

    NetworkStream stream = _connection.GetStream();
    StreamReader input = new StreamReader(stream);
    _output = new StreamWriter(stream);
    
    string line;
    do
    {
        // Poll for data availability.
        while (!stream.DataAvailable)
            Thread.Sleep(300);
    
        line = input.ReadLine();
    
        if (!string.IsNullOrEmpty(line))
        {
            ParseMessage(line);
        }
    }
    while (line != null);
    
    line = input.ReadLine();
    

    【讨论】:

      猜你喜欢
      • 2012-12-04
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2012-06-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多