【问题标题】:C# await on a List<T> CountC# 在 List<T> 计数上等待
【发布时间】:2016-11-18 04:25:11
【问题描述】:

我正在升级一些旧的 WinForms 代码,并试图找出 .NET 4.6.1 的“正确方法”来重构以下内容。

当前代码在检查bool 属性时正在执行紧密的while(true) 循环。此属性将lock() 放在通用List&lt;T&gt; 上,然后如果它没有项目则返回true (list.Count == 0)。

循环中有可怕的Application.DoEvents(),以确保消息泵继续处理,否则会锁定应用程序。

显然,这需要结束。

我的困惑是如何开始一个基本的重构,它仍然可以检查队列长度,同时在线程上执行而不是无缘无故地耗尽 CPU。这里检查之间的延迟很好,即使是像 100 毫秒 + 这样的“长”延迟。

我打算采用一种使方法异步并让任务运行来进行检查的方法:

await Task.Run(() => KeepCheckingTheQueue());

当然,这让我处于需要...循环检查队列状态的方法的情况。

在等待、等待和可用于将这些东西移动到线程池的各种其他方法之间......关于如何最好地处理这个问题的任何建议?

【问题讨论】:

  • 只需启动一个线程进行处理并在完成后引发一个事件。您可以处理事件,无需循环->检查状态。或放入Background Worker 并使用RunWorkerCompleted 事件
  • 你能不能用ConcurrentQueue&lt;T&gt;来替换列表?
  • 可以替换列表类型吗?您能否拥有自己的类来实现IList&lt;T&gt;,由真实列表支持,并在Remove 将计数降至0(或调用Clear)时触发您想要的行为
  • @IanMercer 我可以替换 List 以删除 lock() (我不确定为什么会有锁,代码不使用线程......除非原始开发人员认为这是DoEvents 循环需要?)。我真正的问题是我需要尽可能干净地在 UI 线程上移动这个 while(true) -&gt; check -&gt; DoEvents() 循环。

标签: c# .net multithreading winforms async-await


【解决方案1】:

我需要的是如何最好地“轮询”布尔成员(或属性),同时释放 UI,而不使用 DoEvents()。

你要的答案:

private async Task WaitUntilAsync(Func<bool> func)
{
  while (!func())
    await Task.Delay(100);
}

await WaitUntilAsync(() => list.Count == 0);

但是,像这样的轮询是一种非常糟糕的方法。如果你能描述你的代码正在解决的实际问题,那么你就能得到更好的解决方案。

例如,如果列表表示一些工作队列,并且您的代码希望异步等待它完成,那么可以使用显式信号(例如,TaskCompletionSource&lt;T&gt;)或真正的生产者/消费者来更好地编码队列(例如,TPL 数据流)。

【讨论】:

  • 这是我设想的方法(比我所拥有的更好,但同意,仍然很糟糕)。为了更好地描述这个问题,这是一个 WinForms 的“动画”系统,有点像“假 WPF”的考验。队列包含要设置动画的控件列表,以便它们可以并行完成。 DoEvents() 检查 Control 队列 (List&lt;&gt;) 是否允许这些动画“并行运行”,同时仍然发送消息。由于时间限制和大量资源,我试图避免进行全面、完整的重构。 bool 属性本质上是“全部完成”并返回 `queue.Length ==0'。
  • @Patrick:考虑使用Task.WhenAllTaskCompletionSource&lt;T&gt;AsyncCountdownEvent。我有一个可用的 ACE here
  • 谢谢!我会看看。你提到TaskCompletionSource 很有趣,因为那是我目前打开的 MSDN 选项卡。
【解决方案2】:

我认为你在这里追求的是await Task.Yield()的能力。

class TheThing {
  private readonly List<int> _myList = new List<int>();

  public async Task WaitForItToNotBeEmpty() {
    bool hadItems;
    do {
      await Task.Yield();
      lock (_myList) // Other answers have touched upon this locking concern
        hadItems = _myList.Count != 0;
    } while (!hadItems);
  }
  // ...
}

【讨论】:

    【解决方案3】:

    客户端代码在查询之前担心锁定集合(或在代码中到处使用lock() 块)通常不是一个好主意。最好将这种复杂性封装出来。

    相反,我建议使用 .NET 并发集合之一,例如 ConcurrentBag。不需要创建一个有点贵的Task

    如果您的集合没有太大变化,您可能需要考虑使用 不可变 线程安全集合之一,例如 ImmutableList&lt;&gt;

    编辑:在阅读您的 cmets 后,我建议您使用 WinForms TimerOnApplicationIdleBackgroundWorkerasync 的问题是您仍然需要定期调用它。使用计时器或应用空闲回调提供了使用 GUI 线程的好处。

    【讨论】:

    • 这里的问题是lock() 是我最不关心的问题,因为我已经预料到它绝对没有用,因为当前代码不是多线程的!我需要的是如何在释放 UI 时最好地“轮询”布尔成员(或属性),而不使用 DoEvents()。
    • 如果它不是多线程的,那么为什么首先添加lock()。考虑到List&lt;&gt;.Count 立即返回,为什么需要后台作业或Task
    • 我还不确定为什么。但是没有使用明确的线程代码,这不是任何第三方使用的库。最初的想法是开发人员可能认为在添加 DoEvents() 后需要这样做,但是,为什么?不知道。
    【解决方案4】:

    根据用例,您可以启动后台线程或后台工作程序。或者甚至是一个计时器。

    这些在不同的线程中执行,因此不会锁定其他表单相关代码的执行。 Invoke 如果您必须在 UI 线程上执行操作,请使用原始线程。

    我还建议尽可能避免锁定,例如在实际锁定之前进行检查:

    if (list.Count == 0)
    {
        lock (lockObject)
        {
            if (list.Count == 0)
            {
                // execute your code here
            }
        }
    }
    

    这样,您只有在真正需要时才锁定,并且避免不必要地阻塞您的应用程序。

    【讨论】:

    • 我使用线程、线程池和BackgroundWorkers 编写了太多多线程 WinForms 代码。但是这种方法是否仍然需要紧密循环并检查bool,然后返回完成?我也不想在此处使用Thread.Sleep(),以在检查之间放松 CPU,但如果这是最好的方法,我会这样做。
    猜你喜欢
    • 2019-12-18
    • 1970-01-01
    • 2015-12-22
    • 1970-01-01
    • 2014-11-29
    • 2020-03-07
    • 2011-03-30
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多