【问题标题】:C# Multithreading ModelC# 多线程模型
【发布时间】:2015-01-22 19:08:24
【问题描述】:

我有一个 C# 单线程应用程序,目前正在使用线程池使其成为多线程应用程序。我一直在决定哪种模型可以解决我的问题。

这是我目前的情况

While(1)
{
    do_sometask();
    wait(time);
}

这几乎永远重复。新场景有多个执行上述操作的线程。我可以通过根据我必须执行的任务生成线程数来轻松实现它,其中所有线程都执行某些任务并永远等待。

这里的问题是我可能不知道任务的数量,所以我不能盲目地产生 500 个线程。我考虑过使用线程池,但是因为几乎每个线程都永远循环并且永远不会被释放用于队列中的新任务,所以不确定要使用哪个其他模型。

我正在寻找一个想法或解决方案,我可以打破线程中的循环并释放它而不是等待,但在等待后回来并恢复相同的任务(当时间过去时,使用类似执行最后一个任务时的计时器/检查时间戳)。

有了这个,我可以使用有限数量的线程(例如在线程池中)并为在旧线程等待期间进入的任务提供服务(实际上)。

非常感谢任何帮助。

【问题讨论】:

  • 不要使用带有 while 循环和睡眠的显式线程,而是使用 Timers,只需将计时器的下一个间隔设置为您应该睡觉的时间。取决于which timer you choose,一些使用线程来运行“elapsed”事件,而另一些则在 UI 线程上运行(您可能需要System.Timers.Timer)。

标签: c# multithreading design-patterns


【解决方案1】:

如果你只有一堆周期性发生的事情,听起来你想要的是一堆计时器。为每个任务创建一个计时器,在适当的时候触发。因此,如果您有两个不同的任务:

using System.Threading;

// Task1 happens once per minute
Timer task1Timer = new Timer(
    s => DoTask1(),
    null,
    TimeSpan.FromMinutes(1),
    TimeSpan.FromMinutes(1));

// Task2 happens once every 47 seconds
Timer task2Timer = new Timer(
    s => DoTask2(),
    null,
    TimeSpan.FromSeconds(47),
    TimeSpan.FromSeconds(47);

定时器是一个非常轻量级的对象,所以拥有一大堆它们并不是问题。计时器仅在触发时占用 CPU 资源。回调方法将在池线程上执行。

有一个潜在的问题。如果您有很多计时器都具有相同的周期,那么回调将同时被调用。线程池应该通过限制并发任务的数量来优雅地处理这个问题,但我不能肯定地说。但是,如果您的等待时间是错开的,那么这将很有效。

如果您的等待时间很短(不到一秒),那么您可能需要一种不同的技术。如果需要,我会详细说明。

【讨论】:

  • 谢谢你和@Scott Chamberlain 我知道计时器在资源使用或避免睡眠/等待方面会有所帮助。假设我有一个聊天应用程序,其中每个线程都专门用于检查更新、响应并等待几秒钟并重复任务的成员。实际上,我想要实现的是让“n”个线程来处理“m”个成员。在大多数情况下,成员将不仅仅是线程。这就是我考虑以某种方式使用线程池 + 一些逻辑或类似模型来利用等待时间出来并为新成员服务的原因。希望我解释得更好。
  • @Questions:对于聊天应用程序,您不希望在该级别维护线程。事实上,您可能不想进行投票。在 StackOverflow 和 Google 中搜索 C# 聊天服务器。有很多例子。您可能希望为此使用 WCF。
  • 我只是以聊天应用为例。我的主要问题是避免线程数量,同时不想在实际时间(等待)过去之前再次完成任务。如果我使用一个简单的线程池,我将能够在没有任何等待/循环的情况下执行任务。但我正在寻找关于如何以及何时调用线程而不是调用线程本身的逻辑/模型。即当任务 1 完成并退出线程时,只有在等待时间结束后才能再次调用它。
  • 您需要注意System.Theading.Timer 的一件事,如果您不保留对计时器的引用,它可能会在计时器运行时被垃圾收集。这将停止您的计时器。 System.Timers.Timer 没有这个问题。
  • @ScottChamberlain:虽然这是真的,但System.Timers.Timer 的缺点是压缩了在滴答事件中发生的异常。它也无法将用户数据传递给计时器(State 参数到System.Threading.Timer 构造函数)。 Timers.Timer 的两个优点是它使用事件而不是回调,并且它具有可用于在 UI 线程上执行事件的 SynchronizingObject。但在我看来,这些优点并没有超过缺点。尤其是异常压缩。
【解决方案2】:

使用这种设计,您在任何时候都只有一个线程被阻塞。

让一个线程(主线程)等待并发阻塞集合,例如BlockingCollection。该线程将被TryTake 的调用阻塞,直到将某些东西放入集合中,或者在通过传递给调用的超时经过了一定时间之后(稍后会详细介绍)。

一旦它被解除阻塞,它可能有一个工作单元要处理。它检查是否有一个(即TryTake 调用没有超时),然后是否有能力执行这项工作,如果有,则排队一个线程(池、任务或其他)来服务工作。然后这个主线程返回阻塞集合并尝试获取另一个工作单元。循环继续。

当一个工作单元开始时,会记录下来,以便主线程可以看到有多少线程在工作。本单元完成后,符号将被删除。然后释放线程。

您希望使用超时,以便如果判断出有太多操作同时运行,您将能够在一段时间后重新评估此设置。否则,该工作单元将位于阻塞集合中,直到添加一个新单元,这不是最佳的。

此实例的外部用户只需将新的工作单元放入集合中即可排队。

当需要关闭操作时,您可以使用取消令牌立即解除阻塞线程。让工作人员操作也获取取消令牌,以便它们可以在关闭时停止。

【讨论】:

    【解决方案3】:

    我可以借助线程池和几个条件来实现它,以便在将任务添加到线程池队列之前检查任务的最后一个活动。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2011-08-06
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-12-06
      • 2018-08-13
      • 2014-12-05
      • 2021-10-10
      相关资源
      最近更新 更多