如何正确使用实时优先级

5

我的问题可能不完全是关于实时处理的,但也可能是。

我的应用程序有几个比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阻止表单事件?
除了回答所有令人沮丧的问题之外,是否有某种线程分析器可用于帮助我解决混乱的情况?我尝试使用“Managed Stack Explorer”,但它似乎并不总是显示应用程序的线程。
任何对此事的帮助都将对我有所帮助。

你解决了这个问题吗?我认为我遇到了类似的情况,打开、关闭或绘制窗口(即使是其他进程)会导致实时优先级线程停滞。 - Eugene Ryabtsev
@EugeneRyabtsev,不,这并没有真正“解决”。我正在使用“BeginInvoke”和“线程池”,但都没有显著的改进。确实有帮助的是,在处理完关键事件后,直接在一些关键线程中放置一个延迟。它所做的是为较不关键的线程释放周期时间以进行实际处理。我发现,如果较不关键的线程等待时间过长,它最终会影响到优先级较高的线程。 - Gorchestopher H
2个回答

5

好的,这是一个开始:

Invoke(DelegatedDisplay, Result);

这意味着您正在导致后台线程等待,直到UI线程实际执行绘图操作,然后才继续。从线程的角度来看,这是一种永恒的等待。您可能需要研究UI的异步更新:
BeginInvoke(DelegatedDisplay, Result);

这相当于告诉UI线程“在有机会时执行此绘图操作”,然后继续您正在进行的工作。但是,您应该知道,这可能会导致使用Invoke时未发生的线程安全问题。例如,如果后台线程仍在修改Result而UI尝试绘制,则可能会出现意外的竞争条件。请参见Control.InvokeControl.BeginInvoke

1
如果在调用BeginInvoke之后,您没有修改Result或其内容中的任何内容,那么您应该没问题。 - Chris Shain
这可能是另一个愚蠢的问题,但如果我多次调用声明并启动线程的函数,无论该函数被调用多少次,线程都是单独的线程,即使在线程完成一次之前我两次调用该函数。 - Gorchestopher H
1
.NET 4上的线程池每个CPU默认有250个工作线程。如果你达到了这个限制,那么你会遇到更大的问题——你将花费太多时间在上下文切换上,以至于无法完成任何工作。 - Chris Shain
好的,我正在做正确的事情。虽然我仍然有问题,但是在切换到线程池而不是只创建新线程后,我注意到我的应用程序有了一些微小的改进。 - Gorchestopher H
你试过使用BeginInvoke而不是Invoke吗? - Chris Shain
显示剩余4条评论

1

使用像InvokeBeginInvoke这样的封送技术来更新UI是问题的一部分。实际上,我很少使用封送操作来处理UI和工作线程之间的交互,因为它并不是一个很好的解决方案。坦白地说,在大多数这种情况下,它可能是(通常是)最糟糕的解决方案。

我通常做的是让工作线程将其结果或进度发布到共享数据结构中,并使用System.Windows.Forms.Timer(或DispatcherTimer)轮询UI线程以获得最佳效果的时间间隔。

以下是可能的实现方式。

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来更新UI,以至于他们忽略了更好的解决方案。这已经到了这些调度技术适用于cargo cult programming的地步。我可能因为这个话题而听起来像一个破碎的唱片,因为我一直在批评它。我上面使用的技术具有以下优点。
  • 它打破了调度操作所施加的UI和工作线程之间的紧密耦合。
  • 工作线程不必等待UI线程的响应,就像使用Invoke的情况一样。
  • 没有像使用BeginInvoke那样饱和UI消息队列的机会。
  • 您可以在UI和工作线程上获得更高的吞吐量。
  • UI线程可以决定何时以及多久更新UI线程。
  • 您不必在代码中(我是指文字)添加InvokeBeginInvoke调用。
  • 调度操作很昂贵。
  • 代码最终看起来更优雅。
每次需要并发处理时启动一个新线程会浪费宝贵的时间吗?这应该是一个循环吗?我的循环有问题吗?
我建议避免随意创建线程。如果您可以让线程在循环中运行,那将更好。
我能否给一个线程比其他线程更高的优先级,或者Thread.Sleep是唯一的选择?如果我确实分配了更高的线程优先级,如何确保其他线程甚至能够生存?
在这种情况下,为工作线程分配更高的优先级可能会有所帮助。Thread.Sleep(5)不会睡眠5毫秒。它不是这样工作的。顺便说一下,您可以向Thread.Sleep传递一些特殊值。
Thread.Sleep(0)会让出任何处理器上具有相同或更高优先级的线程。
Thread.Sleep(1)会让出任何处理器上的任何线程。
为什么简单的窗体事件会如此妨碍我的其他线程?有没有办法给我的GUI线程分配更少的资源?如果其他线程的时钟时间不足,我能否使用Thread.Sleep来阻止Form事件?
这是因为您正在使用Invoke。避免封送操作将有助于显著减少延迟,因为它将线程解耦。不要在UI线程上使用Thread.Sleep。UI线程必须保持未阻塞状态才能正常工作。如果您使用我提出的解决方案,则更容易控制UI线程的节流。

哇,我在一个事情上做得很正确,就是不在主线程上使用Thread.Sleep。我想我一直认为运行一个基本上轮询列表的循环总是比在需要更新的时刻启动一个操作更有效率。还有其他人可以评论哪种解决方案更好吗?如果我的GUI更新不是关键性的,是否可以使用BeginInvoke?或者在我的关键线程上使用BeginInvoke会导致效率低下? - Gorchestopher H
经过一堆试错之后,我发现即使删除了所有BeginInvoke和Invoke实例,我的应用程序仍然被GUI搞乱了。此外,我还删除了计时器,并且根本没有进行任何GUI更新,但是我仍然超时了。肯定还有其他阻塞我的重要线程的问题。 - Gorchestopher H

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接