【问题标题】:three c# concurrent queue's - how can I pause them while a particular action needs to happen?三个 c# 并发队列 - 在需要发生特定操作时如何暂停它们?
【发布时间】:2012-11-13 22:03:34
【问题描述】:

我有一个问题,我可以用一些代码示例来帮助我,我觉得提供一些背景知识可能会有所帮助。

我需要创建一个包含 3 个队列的引擎(在 C# 中,winforms)。 3 个队列仅包含一个“动作”对象。动作被扔进引擎,并坚持到“最可用”的队列(基本上是计数最少的队列)。队列几乎在所有时间都可以离散和异步运行而不会造成任何伤害。但是,可能会发生一种“动作”情况,当这种类型的“动作”发生并且确实冒泡到队列的前面时,它必须:

  • 等待其他队列停止当前操作
  • 当他们完成当前操作时锁定/暂停他们
  • 单独运行操作直到完成
  • 释放对其他 2 个队列的锁定。

另外一个问题是 3 个队列中的任何一个都可以锁定其他 2 个。

有人有这方面的经验吗?

希望如此,这似乎有点痛苦 :-) 在此先感谢

【问题讨论】:

  • 这里缺少一个重要信息:那些 SpecialActions - 其中有多少可以同时注册到队列中?是否偶尔会发生一次,并且您可以保证在解决当前问题之前不会出现新的 SpecialAction(如果这是真的,则不必将 SpecialActions 放入队列中!)?或者可能有 100 个这样的 SpecialAction 存储在队列中,并且所有这些都一个接一个地必须停止一切并解决,一次一个(如果这是真的,运行它们的顺序应该是什么)?跨度>
  • 羽蛇神这是个好问题。动作就是这四个。 1)从服务器复制文件 2)将文件复制到服务器 3)删除本地文件 4)重新哈希值文件。特殊操作是文件重新哈希,它相当常见(但速度很快),导致它的事件是文件更改事件。然而,哈希表是所有其他 3 个的查找表,因此暂停
  • @conners:我已经根据其他两个发布了我的建议。

标签: c# winforms locking queue


【解决方案1】:

这是single queue approach recommended by ServyReaderWriterLock suggestion by Casperah 的组合。

ReaderWriterLockSlim throttler = new ReaderWriterLockSlim();
for (int i = 0; i < numWorkers; i++)
{
    Task.Factory.StartNew(() =>
    {
        foreach (Action nextAction in queue.GetConsumingEnumerable())
        {
            if (mustBeExectutedSerially(nextAction))
            {
                try
                {
                    throttler.EnterWriteLock();
                    nextAction();
                }
                finally
                {
                    throttler.ExitWriteLock();
                }
            }
            else
            {
                try
                {
                    throttler.EnterReadLock();
                    nextAction();
                }
                finally
                {
                    throttler.ExitReadLock();
                }
            }
        }
    });
}

【讨论】:

  • 你的读/写锁被反转了;要么,要么您应该将返回布尔值的函数重命名为canExecuteInParallel
【解决方案2】:

首先,我不建议使用三个队列。我建议使用一个队列,只读取 3 个不同的任务。我还建议使用BlockingCollection&lt;T&gt;(它只是ConcurrentQueue 的包装,因为它更易于使用。

至于其余的,ReaderWriterLockSlim (Thanks Casperah) 应该很容易处理它。 Writer 需要独占锁,而 Reader 只锁定其他 writer,这正是您的用例。

var queue = new BlockingCollection<Action>();

int numWorkers = 3;
ReaderWriterLockSlim throttler = new ReaderWriterLockSlim();


for (int i = 0; i < numWorkers; i++)
{
    Task.Factory.StartNew(() =>
    {
        foreach (Action nextAction in queue.GetConsumingEnumerable())
        {
            if (mustBeExectutedSerially(nextAction))
            {
                try
                {
                    throttler.EnterWriteLock();
                    nextAction();
                }
                finally
                {
                    throttler.ExitWriteLock();
                }
            }
            else
            {
                try
                {
                    throttler.EnterReadLock();
                    nextAction();
                }
                finally
                {
                    throttler.ExitReadLock();
                }
            }
        }
    });
}

【讨论】:

  • 谢谢Servy,我会把答案发给你,并不是说其他​​答案是错误的(他们都有优点 - 所以谢谢大家!)但是你的是最灵活的(改变队列号是什么我可能会使用)和最简洁的
  • +1 表示单队列建议。很多时候,它会导致稍微多一点等待,但工人的空闲时间会少得多!
  • 这里有一个竞争条件会导致“致命的拥抱”。如果两个工作人员都将一个必须连续执行的动作出队,他们都将尝试在信号量上执行numWorkers 等待。到那时,所有处理都会停止,因为每个工作人员都在等待信号量,并且没有工作人员会执行释放。
  • @conners:我建议使用此处概述的单个队列,但使用另一个答案中提出的ReaderWriterLock(或者,最好是ReaderWriterLockSlim)。
  • @Jim 实际上我重新阅读了它 - 我不太确定这两种解决方案如何结合,你能发布一些 Jim 吗?我已经发布了“接受”
【解决方案3】:

似乎 System.Threading.ReaderWriterLock 可以为您完成这项工作。

正常的任务应该这样做:

readerWriterLock.AcquireReaderLock(timeout);
try
{
    RunNormalAction();
}
finally
{
     readerWriterLock.ReleaseReaderLock();
}

高级任务应该这样做:

readerWriterLock.AcquireWriterLock(timeout);
try
{
    RunSpecialAction();
}
finally
{
     readerWriterLock.ReleaseWriterLock();
}

您可以根据需要启动任意数量的 ReaderLock,它们将按预期继续运行。 当一个 WriterLock 被获取时,所有的 ReaderLocks 都被释放,并且一次只运行一个 WriterLock。

【讨论】:

  • +1,除非我会推荐System.Threading.ReaderWriterLockSlim,如果这一切都在一个进程中(似乎是这种情况)。
【解决方案4】:

我的谦虚建议:

创建三个对象

object threadlock1 = new object();
object threadlock2 = new object();
object threadlock3 = new object();

每个线程在执行任何操作之前都会获取一个对象的锁定。

lock (threadlock1)  // On thread 1, for example
{ //Run Action }

当 THE 动作到来时,带有 THE 动作的线程必须获得对这三个对象的锁定,从而等待其他线程完成它们的工作,并阻止它们再做任何事情。

lock (threadlock1)  // On thread 1, for example
{
 lock (threadlock2)
 {
  lock (threadlock3)
  {
    //Run THE Action 
  }
 }
}

当 THE 操作完成后,您释放所有三个锁,一切都恢复正常,每个线程都持有自己的锁,并恢复操作。

【讨论】:

  • 请注意,这并不能很好地扩展到 n 个工人。不过,您可以将它们全部放在列表或其他集合中,然后锁定集合中的所有内容以执行特殊操作。
  • 如果您同时/几乎同时在 2 个不同的线程上收到 2 个 THE 操作实例,这可能会死锁。
  • @JMarsch 究竟如何?如果每个线程都要求自己的锁,在其他线程之前,我认为他们不能交换锁,可以吗?
  • @LMB:如果每个线程都以完全相同的顺序请求锁定:1、2、3,那么您就可以了(因此,如果每个线程都严格遵循上面的代码,那么您就是ok. 如果你在别人之前申请你的锁,你可以死锁。例如:线程 1 抓住了 lock1,看看是不是这个动作,然后去申请 lock2。同时,线程 2 已经抓住了 lock2,看到它是另一个动作,然后去锁 1。(线程 1 正在等待锁 2,线程 2 正在等待锁 1,它已死锁)。避免这种情况的一种方法是始终以相同的顺序获取锁。
  • 但是如果你总是以相同的顺序获取锁,那么你不会从拥有多个线程中获得任何好处。您永远不会有两个或更多线程同时工作。
猜你喜欢
  • 1970-01-01
  • 2022-08-08
  • 1970-01-01
  • 2016-06-12
  • 1970-01-01
  • 1970-01-01
  • 2018-07-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多