【问题标题】:Design thoughts required about concurrent processing并发处理所需的设计思想
【发布时间】:2024-05-23 03:05:02
【问题描述】:

我有一系列计算需要处理 - 计算和它们运行的​​顺序都是由用户在 UI 上定义的。

如果他们只是一个接一个地跑,那就不会太难了。但是,某些计算需要同时处理,并且所有计算必须具有随时单独暂停的能力。我还需要能够随时重新安排订单或添加要处理的新计算。所以无论我做什么都必须足够灵活来处理这个问题。

在 UI 上,想象一个用户控件的列表框(如果您愿意,可以是一个队列) - 每个用户控件都显示计算的名称和一个暂停按钮。而且我可以在处理过程中随时将计算添加到此列表中。

最好的方法是什么?

我应该在自己的线程中运行每个计算吗?如果是这样,我应该如何存储正在运行的进程列表?我如何将队列传递给计算处理器?我将如何确保每次队列更改(新排序或新计算)时计算处理器都会意识到这一点?

我最初的想法是:

  • CalcProcessor class
  • CalcCalculation class

CalcProcessor 中有 2 个 ListsCalcCalculations。一个是 UI 上显示的“队列”(可能是指向它的指针?或其他确保实时更新的方式),另一个是当前正在运行的计算列表。

不知何故,我需要让CalcCalculation 在自己的线程中运行以处理计算,并能够处理任何暂停事件。所以我需要一些方法将 UI 中按下的暂停按钮的信息传输到 CalcProcessor 对象,然后传输到正确的 CalcCalculation

编辑以回应 David Hope:

感谢您的回复。

  1. 是的,有 n 个计算,但由于能够添加更多计算以在 UI 上进行处理,这可能随时更改。

  2. 无论如何,他们不需要共享数据。应用程序中有一个设置来指定应该同时运行多少个(即在任何给定时间 10 个,例如队列中的前 10 个 - 当 1 完成队列中的下一个计算时将开始处理)。

  3. 计算将涉及从某个数据源(可能是数据库或文件)获取数据,然后对其进行分析并对该数据执行一些计算。当我说计算需要暂停时,我并不是说暂停线程......我只是说(例如,因为我还没有编写应用程序的这一部分)如果它正在逐行读取数据库并在处理当前行完成时暂停一些实时计算......并在 UI 上未单击暂停按钮时继续 - 如果我能得到,这可以通过像 while(notPaused) 循环这样原始的东西来完成暂停信息从 UI 进入线程。

【问题讨论】:

  • 你能用 C# 5.0 吗? “某些计算需要同时处理”到底是什么意思?都有些什么样的规矩?如果暂停计算是在浪费一个线程,会好吗?
  • 我将使用 4.0,并且线程必须保持活动状态,因为即使计算已暂停,它也需要定期更新日志文件以显示计算仍处于活动状态(尽管已暂停)。并发运行的计算是指它们需要同时运行——计算不依赖于任何其他计算,但由于时间限制,它们必须同时运行。

标签: c# .net concurrency


【解决方案1】:

这里有几个问题:

如何同步 UI 和模型?

我想你把这个弄反了。您的模型不应该有指向您在 UI 中显示的队列的“指针”。相反,队列应该在您的模型中,并且您应该将数据绑定与 INotifyPropertyChangeObservableCollection 一起使用以在 UI 上显示队列。 (至少在 WPF 中是这样的。)

这样,您可以直接从模型中操作队列,它会自动显示在 UI 上。

如何开始和监控计算?

我认为Tasks 非常适合这个。您可以使用Task.Factory.StartNew() 启动Task。由于您的Tasks 似乎需要很长时间才能执行,您可以考虑使用TaskCreationOptions.LongRunning。您还可以使用Task 了解计算何时完成(或者是否因异常而失败)。

如何暂停正在运行的计算?

您可以为此使用ManualReserEventSlim。通常,它会被设置,但如果你想暂停正在运行的Task,你会Reset() 它。计算需要定期在该事件上调用Wait()。如果不与该线程上的计算合作,就不可能合理地暂停正在运行的线程。

如果您使用的是 C# 5.0,更好的方法是使用 PauseToken 之类的东西。

【讨论】:

    【解决方案2】:

    在 Framework 4.5 中,这里的答案是 Async API,它消除了管理线程的需要。详情请看the async/await关键字。

    从更广泛的角度来看,“CalcProcessor”类是个好主意,但我认为 Task 对象足以取代“CalcCalculation”类。处理器可以简单地拥有一个可枚举的任务。如果需要,处理器可以公开管理队列的方法,以及返回有关其状态的信息。当您的应用程序最终达到必须有结果的状态时,您可以使用 AwaitAll 方法阻塞 CalcProcessor 的线程,直到所有任务完成。

    如果没有关于此处实际目标的更多信息,很难给出更好的建议。

    【讨论】:

    • “Async API”并没有消除管理线程的需要。如果您希望 CPU 密集型计算并行发生,您仍然需要将其显式发送到另一个线程,通常使用 Task.Run()
    • 一般来说,你最好让框架决定它是否应该存在于另一个线程中。您仍然需要管理任务执行,但您的操作比单个线程更高、更抽象。
    • 但框架并没有真正决定这一点,即使你使用await。决定代码在哪里运行的规则非常严格,框架不能仅仅决定它将在另一个线程上运行一些代码。如果你想要并行处理,你真的应该明确说明(就像我说的,使用Task.Run())。
    【解决方案3】:

    您可以使用Observer Pattern 在 UI 上显示结果并将更改命令返回到处理器。 StateCommand 模式将帮助您开始、暂停、取消计算。这些模式以设计方式很好地回答了您的问题。并发性仍然是一个问题,它们不能解决多线程问题,但它们为管理线程开辟了一条更容易的道路。

    【讨论】:

    • 谢谢,这看起来很适合我。不过我有一个问题......大概我需要在每个 CalcCalculation 中使用唯一标识符,以便在调用该方法时有某种方法可以将其与正确的对象匹配,从而暂停正确的计算?这是正确的吗?另外...说 CalcCalculation 有一个 Run() 方法,该方法在新线程中开始计算...我不确定如何将暂停信息传递到正在运行的线程中?这可能是一个简单的问题 - 我过去在线程方面做过一些工作,但我不记得有一次我必须传递信息 IN
    【解决方案4】:

    我建议您没有将问题分解得足够远,这就是您感到沮丧的原因。

    您需要从小处着手,然后从那里开始积累。您提到,但没有定义您的实际要求,但它们似乎是......

    1. 需要能够运行?N?计算
    2. 有些需要同时运行(这是否意味着它们共享数据,如果是,您将如何共享数据)
    3. 必须能够暂停计算(不要使用 Thread.Suspend,因为它可能会使线程处于不稳定状态,如果您正在共享数据尤其糟糕),因此您需要在每次计算中建立暂停点.还需要考虑如何将暂停/取消暂停传达给计算

    就方法而言,有几个需要考虑...

    线程是一个显而易见的选择,但也需要小心照料(启动、暂停、停止等...)

    您也可以使用 BackGroundWorker 或 Parallel.ForEach

    BackGroundWorker 包含用于取消工作人员并提供进度的框架(这可能很有用)。

    我的建议是使用 BackGroundWorker,可能将其子类化以添加您需要的暂停/恢复功能。确定您将如何管理数据共享(至少使用锁定来防止同时访问)。

    您可能会发现 BackGroundWorker 限制太多,需要使用 Threads,但我通常能够避免它。

    如果您发布更明确的要求,或者您尝试过但没有成功的示例,我很乐意提供更多帮助。

    【讨论】:

    • 感谢您的回复,我正在编辑我的原始帖子,因为我的回复太长,无法放在这里。
    【解决方案5】:

    对于队列,您可以使用堆数据结构(优先队列)。这将有助于确定您的任务的优先级。此外,您应该使用线程池进行有效计算。并尝试将您的任务分成小部分。

    【讨论】:

      最近更新 更多