C#:为迭代正确锁定队列

C#:为迭代正确锁定队列,c#,multithreading,locking,queue,synchronized,C#,Multithreading,Locking,Queue,Synchronized,我使用队列(C#)来存储必须发送到任何连接的客户端的数据 我的lock语句为私有只读: private readonly object completedATEQueueSynched = new object(); 只有两种方法正在排队: 1) 由鼠标移动启动,由mainform线程执行: public void handleEddingToolMouseMove(MouseEventArgs e) { AbstractTrafficElement de = new... s

我使用队列(C#)来存储必须发送到任何连接的客户端的数据

我的lock语句为私有只读:

private readonly object completedATEQueueSynched = new object();
只有两种方法正在排队:

1) 由鼠标移动启动,由mainform线程执行:

public void handleEddingToolMouseMove(MouseEventArgs e)
{
    AbstractTrafficElement de = new...
    sendElementToAllPlayers(de)
    lock (completedATEQueueSynched)
    {
       completedATEQueue.Enqueue(de);
    }
}
2) 在按钮事件上启动,也由mainform线程执行(此处无所谓,但安全性优于抱歉):

此方法由负责连接的特定客户端的线程调用。这是:

private void sendSetData(TcpClient c)
{
    NetworkStream clientStream = c.GetStream();
    lock (completedATEQueueSynched)
    {
        foreach (AbstractTrafficElement ate in MainForm.completedATEQueue)
        {
            binaryF.Serialize(clientStream, ate);
        }
    }
}
如果客户端连接,而我同时移动鼠标,则会发生死锁。 如果我只锁定迭代,将抛出InvalidOperation执行,因为队列已更改

我也尝试过同步队列包装器,但它不适合迭代。(即使与锁结合使用) 有什么想法吗?我只是不明白我的错误

看起来像是你想要的

更新


是,工作正常,TryDequeue在Interlocated.CompareExchange和SpinWait中使用。锁不是好的选择,因为太贵了,看一看就别忘了

Her从ConcurrentQueue排队,正如您所看到的,只有
SpinWait
互锁。使用增量
。看起来很不错

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;
  }
公共作废排队(T项)
{
SpinWait SpinWait=新的SpinWait();
而(!this.m_tail.TryAppend(item,ref this.m_tail))
spinWait.SpinOnce();
}
内部空隙增长(参考ConcurrentQueue.段尾)
{
this.m_next=新的ConcurrentQueue.Segment(this.m_索引+1L);
tail=this.m_next;
}
内部bool TryAppend(T值,参考ConcurrentQueue.Segment tail)
{
如果(该高度>=31)
返回false;
int指数=32;
尝试
{
}
最后
{
索引=联锁增量(参考此m_高);

如果(index您可以减少争用,可能足以使其可接受:

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);
    }    
}
private void sendSetData(TcpClient c)
{
i可数列表;
锁(completedATEQueueSynched)
{
list=MainForm.completedATEQueue.ToList();//拍摄快照
}
NetworkStream clientStream=c.GetStream();
foreach(列表中的AbstractTrafficElement ate)
{
序列化(clientStream,ate);
}    
}

当然,快照引入了它自己的计时逻辑。“所有元素”在任何给定时刻的确切含义是什么?

如果你排队、出队的速度不是很高,Henk Holterman的方法是好的。在这里,我想你是捕捉鼠标移动的。如果你希望生成大量数据,我会n队列上述方法不好。锁成为网络代码和排队代码之间的争用。此锁的粒度在整个队列级别


在本例中,我将推荐GSerjo所提到的-ConcurrentQueue。我已经研究了这个队列的实现。它非常精细。它在队列中的单个元素级别上运行。当一个线程退出队列时,其他线程可以不停地并行排队。

不知道这是否有效?您的dea具体位置在哪里dlocking?如果不知道谁调用了全局中的什么,就很难提出同步问题是什么。最后一部分是否在新客户端连接时运行?还要记住,从锁内部引发事件是个坏主意-事件是同步的(默认情况下),因此您可以通过事件处理程序在另一个线程中调用锁外的某些代码,然后尝试重新输入您的锁,这意味着您的事件调用永远无法完成-死锁!我不认为您实际上是死锁,只是阻塞(太)长。但是如果(未来版本的)
Queue
执行了
锁定(此)
?这主要是一个理论上的论点,但你想锁定一些只有你的代码才能访问的东西。
私有
是不够的。Iirc foreach实际上并不出列;你打算出列吗?(你通常是这样做的,有一个队列)解列工作正常吗?由于我使用列表来组织连接的客户机,如果我知道我的锁出了什么问题会更好。锁工作正常,TryDequeue在interlocated.compareeexchange和SpinWait.GSerjo中使用,您的建议非常好,尽管获取锁本身并不是一个非常昂贵的操作。@HenkHolterman给出的建议解决了这个问题:锁在一个可能较慢的操作(网络序列化)期间被持有。按照Henk的建议重写代码将减少队列争用,并可能给出预期的结果。:)最后一个问题:当我在迭代时排队时,队列会发生什么情况?事实证明,问题是服务器开始发送新元素(当我移动鼠标时)当他同时发送旧数据时,解列和序列化停止工作。非常感谢concurrentQueue!我还没有读到关于自旋锁的内容,但据我所知,迭代对队列的作用就像迭代开始时存在的一样,对吗?所以迭代时排队的项目不会出现在迭代中?好建议tion!您只需要知道,在网络传输过程中,可能会有更多的元素排队,很难说代码是否正确,但这可能需要解决=)@sgorozco-这就是我在最后一段中暗示的。它似乎只与Enqueue和GetEnumerator有关,这意味着添加的速率不能达到be非常高。@HenkHolterman我没有收到你的评论。我说的“添加速率”是指调用方每秒调用
排队
的次数。如果这个速率很高,系统的线程争用率就会上升。
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);
    }    
}