2016-10-17 8 views
-1

重複するI/Oを使用するために複数のスレッドを使用するクラスを変換しようとしています。それはほとんど動作していますが、それはスレッドの問題に無作為に当たっているようです。非同期ネットワークストリーム連続読み取りデザインパターン

直接投稿するコードが多すぎますが、ここに基本パターンがあります。目標は、接続が廃棄されるまで接続からデータを読み取ってそこに座ることです。したがって、EndRead()が完了すると、新しいBeginRead()が開始されます。

public enum State 
{ 
    Idle, 
    BeforeRead, 
    PendingRead, 
    FinishingRead, 
    Died, 
} 

private int state; 
private IAsyncResult asyncResult; 
private byte[] readBuffer = new byte[4096]; 
private System.Net.Sockets.NetworkStream stream; 
public void Connect(System.Net.Sockets.TcpClient client, string host, int port) 
{ 
    client.Connect(host, port); 
    this.stream = client.GetStream(); 
} 

private bool SetState(State expectedState, State newState) 
{ 
    return Interlocked.CompareExchange(ref this.state, (int)newState, (int)expectedState) == expectedState; 
} 
public void BeginRead() 
{ 
    try 
    { 
     while (true) 
     { 
      if (!SetState(State.Idle, State.BeforeRead)) 
       return; 
      IAsyncResult async; 
      async = stream.BeginRead(readBuffer, 0, readBuffer.Length, x => EndRead(true), null); 
      if (async == null) 
       return; 
      SetState(State.BeforeRead, State.PendingRead); 
      lock (this) 
       this.asyncResult = async; 
      if (async.AsyncWaitHandle.WaitOne(0)) 
       EndRead(false); 
     } 
    } 
    catch { this.state = State.Died; } 
} 
private void EndRead(bool asynchronousCallback) 
{ 
    try 
    { 
     if (!SetState(State.PendingRead, State.FinishingRead)) 
      return; 
     IAsyncResult async; 
     lock (this) 
     { 
      async = this.asyncResult; 
      this.asyncResult = null; 
     } 
     if (async == null) 
      return; 
     int bytesRead = stream.EndRead(async); 
     HandleData(bytesRead, readBuffer); 
     SetState(State.FinishingRead, State.Idle); 
     if (asynchronousCallback) 
      BeginRead(); 
    } 
    catch { this.state = State.Died; } 
} 

それが動作時間のほとんどは、時折、それはいくつかのいずれかを実行します。

  • メッセージを受信

    • 停止はI asyncResult has already been handled: "EndReceive can only be called once for each asynchronous operation"例外をスローします。

    また、別のスレッド(stream.Write、stream.BeginWriteではなく)から同期書き込みが行われていることにも言及してください。私は読み書きが互いに独立しているべきだと思うので、行動に影響を与えるべきではない。

    私のデザインに根本的に欠陥がありますか?これは切り捨てられた例ですので、私が取り除いたものが問題を引き起こす可能性がありますが、基本的な設計が有効かどうかを知る必要があります。非同期でチェーンを読み取る適切な方法は何ですか?

    (そして場合には提案がasync/awaitを使用することですそれはオプションではありませんので、このコードは、Windows XP上で実行する必要があります。)

  • +0

    はあなたの「ストリーム」変数のデータ型を含めてくださいでした。 – Andrew

    +0

    これはSystem.Net.Sockets.NetworkStreamです(ただし、私のライブコードでは状況に応じてNegotiateStreamまたはそのどちらでもあります)。 –

    +0

    私はBegin/Endパターンを長い時間前に使用していますが、 'BeginRead'からのIAsyncResultの戻り値が' AsyncCallback'で提供されたものと同じでないかもしれないというような奇妙な問題があったことを覚えています。 'AsyncCallback'(それはあなたが使用していない' x')のインスタンスで 'EndRead'を呼び出します。 さらに、 'SetState'を使った状態管理が間違っているようです。非同期コールバックは 'SetState'の前に呼び出すことができます。 また、非同期I/Oを実行してから無限ループで待機する点は何ですか?非同期コールバックからの読み取りをそのまま続行してください。 – Honza

    答えて

    0

    あなたは競合状態を持っている:

     IAsyncResult async; 
         async = stream.BeginRead(readBuffer, 0, readBuffer.Length, x => EndRead(true), null); 
    
         /* Race condition here */ 
    
         if (async == null) 
          return; 
         SetState(State.BeforeRead, State.PendingRead); 
         lock (this) 
          this.asyncResult = async; 
    

    あなたEndReadSetStateの前および/またはthis.asyncResult = asyncが実行される前に実行できます。あなたはこれをすることはできません。状態はに設定してからBeginReadを発行し、失敗した場合はリセットする必要があります。保持し、メンバーasyncResultを使用していますが、その代わりBeginReadにコールバックを渡すと、コールバックで非同期結果を得ることはありません:

    SetState(State.BeforeRead, State.PendingRead); 
        stream.BeginRead(readBuffer, 0, readBuffer.Length, EndRead); 
    
    ... 
    
        private void EndRead(IAsyncResult asyncResult) { 
        int bytesRead = stream.EndRead(asyncResult); 
        ... 
        } 
    
    +0

    @Honzaへのコメントで述べたように、EndReadがBeginReadへの呼び出しから同期して発生し、次のBeginReadをトリガーすると、スタックオーバーフローに再帰する可能性があります。 BeginReadの戻り値を使用できない場合、このシナリオを回避するにはどうすればよいですか? –

    関連する問題