我正在升级一些遗留的 WinForms 代码,我试图找出 .NET 4.6.1 的“正确方法”来重构以下内容。< / p>
当前代码在检查while(true)
属性时执行紧密bool
循环。此属性将lock()
放在通用List<T>
上,如果没有项目(list.Count == 0
)则返回true。
循环中有可怕的Application.DoEvents()
以确保消息泵继续处理,否则会锁定应用程序。
显然,这需要进行。
我的困惑是如何启动一个基本的重构,它仍然可以检查队列长度,同时在线程上执行而不是无缘无故地吹灭CPU。这里检查之间的延迟很好,即使像100ms +这样的“长”也是如此。
我打算采用一种让方法异步的方法,让任务运行来检查:
await Task.Run(() => KeepCheckingTheQueue());
当然,这使我处于需要循环以检查队列状态的方法的情况。
在等待,等待和各种其他方法之间,可以用来将这些东西移动到线程池......关于如何最好地处理这个问题的任何建议?
答案 0 :(得分:4)
我需要的是如何在释放UI时最好地“轮询”布尔成员(或属性),而不使用DoEvents()。
你要求的答案:
private async Task WaitUntilAsync(Func<bool> func)
{
while (!func())
await Task.Delay(100);
}
await WaitUntilAsync(() => list.Count == 0);
然而,像这样的民意调查是一种非常糟糕的方法。如果您可以描述代码正在解决的实际问题,那么您可以获得更好的解决方案。
例如,如果列表代表某个工作队列,并且您的代码想要异步等待直到完成,那么可以使用显式信号(例如TaskCompletionSource<T>
)或真正的生成器更好地编码/消费者队列(例如,TPL数据流)。
答案 1 :(得分:1)
对于客户端代码而言,在查询之前担心锁定集合(或在任何地方用lock()
块填充代码)通常都不是一个好主意。最好将这种复杂性封装起来。
相反,我建议使用其中一个.NET并发集合,例如ConcurrentBag
。无需创建有点贵的Task
。
如果您的收藏品没有太大变化,您可能需要考虑一个不可变的线程安全的收藏集,例如ImmutableList<>
。
编辑:在阅读您的评论后,我建议您使用WinForms Timer
; OnApplicationIdle
或BackgroundWorker
。 async
的问题是您仍需要定期调用它。使用计时器或应用程序空闲回调提供了使用GUI线程的好处。
答案 2 :(得分:0)
根据用例,您可以启动后台线程或后台工作程序。或者甚至可能是计时器。
这些是在不同的线程中执行的,因此不会锁定其他表单相关代码的执行。如果您必须在UI线程上执行操作,则Invoke
原始线程。
我还建议尽可能地防止锁定,例如在实际锁定之前进行检查:
if (list.Count == 0)
{
lock (lockObject)
{
if (list.Count == 0)
{
// execute your code here
}
}
}
这样,只有你确实需要锁定才能避免不必要的应用程序阻塞。
答案 3 :(得分:0)
我认为你在此之后的是能够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);
}
// ...
}