2

接続しているクライアントに送信する必要のあるデータを格納するためにキュー(C#)を使用しています。

私のロックステートメントはプライベート読み取り専用です:

private readonly object completedATEQueueSynched = new object();

エンキューしているメソッドは2つだけです。

1)mainform-threadによって実行される、mouse-movementによって開始されます。

public void handleEddingToolMouseMove(MouseEventArgs e)
{
    AbstractTrafficElement de = new...
    sendElementToAllPlayers(de)
    lock (completedATEQueueSynched)
    {
       completedATEQueue.Enqueue(de);
    }
}

2)mainform-threadによっても実行されるbutton-eventで開始されます(ここでは重要ではありませんが、申し訳ありませんが安全です):

public void handleBLC(EventArgs e)
{
    AbstractTrafficElement de = new...
    sendElementToAllPlayers(de);
    lock (completedATEQueueSynched)
    {
         completedATEQueue.Enqueue(de);
    }
}

このメソッドは、接続されている特定のクライアントを担当するスレッドによって呼び出されます。ここにあります:

private void sendSetData(TcpClient c)
{
    NetworkStream clientStream = c.GetStream();
    lock (completedATEQueueSynched)
    {
        foreach (AbstractTrafficElement ate in MainForm.completedATEQueue)
        {
            binaryF.Serialize(clientStream, ate);
        }
    }
}

クライアントが接続し、同時にマウスを動かしている場合、デッドロックが発生します。反復のみをロックすると、キューが変更されたため、InvalidOperationexectionがスローされます。

同期されたQueue-Wrapperも試しましたが、反復処理では機能しません。(ロックと組み合わせても)何かアイデアはありますか?間違えない

4

3 に答える 3

3

競合を減らすことができます。おそらく、それを受け入れられるようにするのに十分です。

private void sendSetData(TcpClient c)
{
    IEnumerable<AbstractTrafficElement> list;

    lock (completedATEQueueSynched)
    {
        list = MainForm.completedATEQueue.ToList();  // take a snapshot
    }

    NetworkStream clientStream = c.GetStream();
    foreach (AbstractTrafficElement ate in list)
    {
       binaryF.Serialize(clientStream, ate);
    }    
}

しかしもちろん、スナップショットには独自のタイミング ロジックが導入されます。特定の瞬間における「すべての要素」とは正確には何を意味するのでしょうか?

于 2012-08-01T21:56:14.293 に答える
2

あなたが望んでいたConcurrentQueueのように見えます

アップデート

はい、正常に動作します。TryDequeueはInterlocked.CompareExchangeおよびSpinWait内で使用します。コストが高すぎるため、SpinLockを確認し、並列プログラミングのデータ構造を 忘れないようにするため、ロックは適切な選択ではありません。

ご覧のとおり、彼女はConcurrentQueueからエンキューされてSpinWaitおりInterlocked.Increment、使用されています。かなり素敵に見えます

public void Enqueue(T item)
{
  SpinWait spinWait = new SpinWait();
  while (!this.m_tail.TryAppend(item, ref this.m_tail))
    spinWait.SpinOnce();
}

  internal void Grow(ref ConcurrentQueue<T>.Segment tail)
  {
    this.m_next = new ConcurrentQueue<T>.Segment(this.m_index + 1L);
    tail = this.m_next;
  }

  internal bool TryAppend(T value, ref ConcurrentQueue<T>.Segment tail)
  {
    if (this.m_high >= 31)
      return false;
    int index = 32;
    try
    {
    }
    finally
    {
      index = Interlocked.Increment(ref this.m_high);
      if (index <= 31)
      {
        this.m_array[index] = value;
        this.m_state[index] = 1;
      }
      if (index == 31)
        this.Grow(ref tail);
    }
    return index <= 31;
  }
于 2012-08-01T21:36:14.470 に答える
1

Henk Holterman のアプローチは、キューへのエンキュー、キューでのデキューの割合がそれほど高くない場合に適しています。ここで、マウスの動きをキャプチャしていると思います。キューで大量のデータを生成することが予想される場合、上記のアプローチは適切ではありません。ロックは、ネットワーク コードとエンキュー コードの間の競合になります。このロックの粒度は、キュー全体のレベルです。

この場合、GSerjo が言及したConcurrentQueueをお勧めします。このキューの実装を調べました。非常に粒状です。キュー内の単一要素レベルで動作します。1 つのスレッドがデキューしている間、他のスレッドは停止することなく並行してエンキューできます。

于 2012-08-01T22:54:12.553 に答える