【问题标题】:How to Properly Use Realtime Priority如何正确使用实时优先级
【发布时间】:2012-05-31 13:11:04
【问题描述】:

我的问题可能不是关于实时处理,但话又说回来,它可能是。

我的应用程序有几个比 GUI 重要得多的线程,但是,我确实希望 GUI 至少可以使用。我不希望它一直被锁定,我确实想根据我正在执行的处理结果更新屏幕。

目前我所有的基本项目都隔离在单独的线程中,我调用一个委托到我的 GUI 来显示结果。

我的 GUI 可以正常工作,但如果我更改选项卡或最小化/最大化它,已知会阻碍我的其他线程,使其无法在 0.1 秒的时间限制内执行操作。

这就是我打电话给我的代表的原因:

delegate void FuncDelegate(ResultContainer Result);
FuncDelegate DelegatedDisplay= new FuncDelegate(DisplayResults);
//then later on
Invoke(DelegatedDisplay, Result);

我的大部分关键进程都是在连续循环中运行的线程,从各种缓冲区(ArrayLists 和普通 Lists)拉入和推入。

每次都会启动我的一个关键线程,使用:

Thread mythread = new Thread(new ThreadStart(ProcessResults));
mythread.Start();

我想这样做的原因,而不是仅仅让一个线程在循环中运行,从列表中提取,是因为我认为我的时钟时间用完的原因可能是我担心有一个轮询循环消耗太多资源(尽管每次民意调查结果为负时我都使用 Thread.Sleep(5))。

每次我需要并发进程时启动一个新线程是否会花费我宝贵的时间?这应该是一个循环吗?应该怪我的循环吗?

我可以给一个线程比其他线程更高的优先级,还是使用 Thread.Sleep 是我唯一的选择?如果我确实分配了更高的线程优先级,我如何确定其他线程甚至可以生存?

为什么简单的表单事件会严重阻碍我的其他线程?有没有办法给我的 GUI 线程一个分配的、更少的资源?如果其他线程的时钟时间用完,我可以使用 Thread.Sleep 以某种方式阻止 Form 事件吗?

我所有令人沮丧的问题都没有得到答案,是否有某种线程分析器可以用来帮助解决我的问题?我尝试使用“托管堆栈资源管理器”,但不知何故,它并不总是显示我的应用程序有哪些线程。

在这件事上的任何帮助都会对我有很大帮助。

【问题讨论】:

  • 您解决了这个问题吗?我想我遇到过类似的情况,打开-关闭-绘图窗口(甚至是其他进程)以某种方式停止了实时优先级线程。
  • @EugeneRyabtsev 不,这并没有真正“解决”。我正在使用“BeginInvoke”和“线程池”。两者都没有做出重大改进。有什么帮助是在处理关键事件后直接让部分关键线程进入睡眠状态。这样做的目的是释放周期时间,让不太关键的线程实际处理。我发现如果一个不太重要的线程必须等待太久,它最终会影响更高优先级的线程。

标签: c# winforms multithreading real-time


【解决方案1】:

这是一个开始:

Invoke(DelegatedDisplay, Result);

这意味着您正在使后台线程等待,直到 UI 线程实际执行绘图操作,然后继续。从线程的角度来看,这是永恒的。您可能想要调查 UI 的异步更新:

BeginInvoke(DelegatedDisplay, Result);

这相当于告诉 UI 线程“当你有机会时,执行这个绘图动作”,然后继续你正在做的工作。

您应该知道,这可能会导致使用 Invoke 时不会出现的线程安全问题。例如,如果在 UI 尝试绘制时后台线程仍在修改 Result,您可能会遇到意外的竞争条件。

参见Control.InvokeControl.BeginInvoke

【讨论】:

  • 我的问题中显示的 "MyThread" 是当前调用的内容,它的作用是从列表中删除结果,然后进行通信,然后使用该结果进行调用。在这种情况下我需要保护“结果”吗?结果是我自己的类,所以我可以将新结果声明为副本,然后将其传递到我的 BeginInvoke 中吗?我仍然很难知道何时抛出指针以及何时生成新数据。
  • 如果你在调用BeginInvoke后没有修改Result或“MyThread”上的任何内容,你应该没问题。
  • 这可能是另一个愚蠢的问题,但是如果我多次调用一个声明并启动一个线程的函数,无论该函数被调用多少次,线程是 i> 分离线程,即使我在线程完成一次之前调用该函数两次。
  • 这是正确的,假设您按照问题中的描述进行操作。您应该知道,使用线程池通常比为低延迟工作创建新线程更高效。创建线程需要 长时间 时间。套用 Eric Lippert 的话说,“假设启动一个线程大约需要 100 年,因为对于处理器而言,这大约需要多长时间”。
  • .NET 4 上的线程池默认每个 CPU 有 250 个工作线程。如果你达到了这个限制,你就会遇到更大的问题——你将花费大量时间进行上下文切换,以至于你永远无法完成任何工作。
【解决方案2】:

使用诸如InvokeBeginInvoke 之类的编组技术来更新用户界面是问题的一部分。事实上,我很少将封送操作用于 UI 和工作线程交互,因为它不是一个很好的解决方案。好吧,坦率地说,在这种性质的大多数情况下,它可能(而且通常是)最糟糕的解决方案。

我通常做的是让工作线程将其结果或进度发布到共享数据结构,并让 UI 线程使用System.Windows.Forms.Timer(或DispatcherTimer)在调整为最佳工作的时间间隔内对其进行轮询就目前的情况而言。

这可能是它的样子。

public class YourForm : Form
{
  private ConcurrentQueue<ResultContainer> results = new ConcurrentQueue<ResultContainer>();

  public UpdateTimer_Tick(object sender, EventArgs args)
  {
    // Limit the number of results to be processed on each cycle so that
    // UI does not stall for too long.
    int maximumResultsToProcessInThisBatch = 100;

    ResultContainer result;
    for (int i = 0; i < maximumResultsToProcessInThisBatch; i++)
    {
      if (!results.TryDequeue(out result)) break;
      UpdateUiControlsHere(result);
    }
  }

  private void WorkerThread()
  {
    while (true)
    {
      // Do work here.
      var result = new ResultContainer();
      result.Item1 = /* whatever */;
      result.Item2 = /* whatever */;
      // Now publish the result.
      results.Enqueue(result);
    }
  }
}

问题是,人们已经被编程为自动使用InvokeBeginInvoke 来更新用户界面,以至于他们忽略了更好的解决方案。它已经到了这些编组技术适合cargo cult programming 领域的地步。关于这个话题,我可能听起来像是一个破纪录,因为我一直在撕扯它。我上面使用的技术有以下优点。

  • 它打破了编组操作强加的 UI 和工作线程之间的紧密耦合。
  • 工作线程不必像 Invoke 那样等待来自 UI 线程的响应。
  • 不会像 BeginInvoke 那样使 UI 消息队列饱和。
  • 您可以在 UI 和工作线程上获得更高的吞吐量。
  • UI 线程可以决定何时以及多久更新一次 UI 线程。
  • 您不必在调用 InvokeBeginInvoke 时乱扔代码(我的意思是字面意思)。
  • 编组操作成本高昂。
  • 代码最终看起来更优雅。

每次我需要并发进程时是否启动一个新线程 浪费我宝贵的时间?这应该是一个循环吗?是我的循环问题吗?

我会避免随意创建线程。如果你能保持线程在循环中运行会更好。

我可以给一个线程比其他线程更高的优先级,或者是使用 Thread.Sleep 我唯一的选择?如果我确实分配了更高的线程优先级, 我怎么能确定其他线程甚至可以生存?

在这种情况下,为您的工作线程提供更高的优先级可能会有所帮助。 Thread.Sleep(5) 不会睡 5 毫秒。它只是不那样工作。顺便说一下,您可以将一些特殊值传递给Thread.Sleep

  • Thread.Sleep(0) 让位于任何处理器上具有相同或更高优先级的任何线程。
  • Thread.Sleep(1) 屈服于任何处理器上的任何线程。

为什么简单的表单事件会严重阻碍我的其他线程?是 有一种方法可以给我的 GUI 线程分配一个较低数量的 资源?我可以使用 Thread.Sleep 以某种方式阻止表单事件,如果 其他线程的时钟时间用完了?

这是因为您使用的是Invoke。避免编组操作将有很大帮助,因为它可以解耦线程。不要在 UI 线程上使用 Thread.Sleep。 UI 线程必须保持畅通状态才能正常工作。如果您使用我在上面提出的解决方案,那么限制 UI 线程会容易得多。

【讨论】:

  • 哇,我正在做一件正确的事情,即不在主线程上使用 Thread.Sleep。我想我一直认为运行一个基本上轮询列表的循环总是比在我需要它的时候启动更新操作效率低。其他人可以评论哪个解决方案更好吗?如果我的 GUI 更新不重要,是否可以使用 BeginInvoke?还是使用 BeginInvoke 会导致所有地方的效率低下,甚至在我的关键线程上也是如此?
  • 经过大量试验和错误后,我发现我的应用程序仍然被 GUI 破坏,即使在删除了所有实例 BeginInvoke 和 Invoke 之后也是如此。此外,我删除了计时器,根本没有更新 GUI,而且我仍然会超时。一定有其他事情正在阻塞我的重要线程。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2018-07-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2018-05-04
  • 1970-01-01
相关资源
最近更新 更多