1

接続が長時間 (数時間または一晩) 開かれた後、マルチスレッド アプリケーションで TcpClient が送信タイムアウトで終了するという問題が発生しています。NetworkStream は、UI スレッドとバックグラウンド スレッドの 2 つのスレッドによって使用されています。受信データの読み取りに使用される StreamReader と、送信データに使用される StreamWriter があります。StreamReader は 1 つのスレッド (バックグラウンド スレッド) にしかアクセスされませんが、StreamWriter は UI スレッドとバックグラウンド スレッドの両方からアクセスされます。

接続を開いてリモートサーバーに接続すると、問題なくすぐにデータを送受信できます. 送信タイムアウトは発生せず、データは正しく送受信されます。ただし、その後立ち去って数時間データを送信せずに戻ってデータの送信を開始すると (これが意味を成すのに役立つ場合、これはチャット アプリケーションです)、ソケットは送信時にタイムアウトします。私が立ち去る間、データの受信にはまったく問題はありません。さらに、リモート サーバーはアクティブな接続をポーリングし、クライアントはそれに応答する必要があります。接続は数時間開いているため、応答を正しく送信している必要があります。ただし、このポーリング応答はバックグラウンド スレッドでのみ送信されます。入力したデータは UI スレッドから送信され、そこでタイムアウトが発生します。

同時アクセスと関係があると思いますが、何が原因なのか、UIから問題なく最初にデータを送信でき、数時間アイドル状態になった後にタイムアウトするだけな理由がわかりません。

以下は関連するコードです。上部の変数はクラスで宣言されています。Address と Port はクラスのプロパティです。WriteLine は、アプリケーション内で StreamWriter を使用してデータを送信する唯一のメソッドです。同期の問題が修正されることを期待して、StreamWriter.WriteLine の呼び出しをロックしました。WriteLine は、ParseMessage 内のバックグラウンド スレッドから呼び出され、他の場所では UI から呼び出されます。

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();
    }
}
4

1 に答える 1

1

クラスのブロッキング メソッド (ReadおよびWrite) は、NetworkStream複数のスレッドから同時に使用するようには設計されていません。MSDN から:

シンプルなシングル スレッドの同期ブロッキング I/O には、 メソッドWriteとメソッドを使用します。Read個別のスレッドを使用して I/O を処理する場合は、BeginWriteEndWriteメソッド、またはBeginReadEndReadメソッドを通信に使用することを検討してください。

私の仮定では、 UI スレッドからWriteLine(および内部的にNetworkStream.Write) を呼び出すと、同時ReadLine(内部的にNetworkStream.Read) 操作がバックグラウンド スレッドで完了するまでブロックされます。後者が 内で実行しない場合、SendTimeoutWriteタイムアウトします。

この問題を回避するには、非ブロッキング メソッドを使用するように実装を変換する必要があります。ただし、これが本当に問題であるかどうかを最初にテストするための簡単なハックとして、次のDataAvailable前にポーリングを導入してみてくださいReadLine

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-06-02T20:36:40.623 に答える