延迟执行的最佳方法
Best way to delay execution
假设我有一个方法,我通过 Task.Factory.StartNew()
在单独的线程中 运行。
此方法报告的进度如此之多 (IProgress
),以至于它冻结了我的 GUI。
我知道简单地减少报告数量是一种解决方案,比如只报告十分之一,但就我而言,我真的想获取所有报告并将它们显示在我的 GUI 中。
我的第一个想法是将所有报告排队并逐一处理,每份报告之间稍作停顿。
第一:这是一个好的选择吗?
其次:如何实现?使用计时器或使用某种 Task.Delay()
?
更新:
我会尽力解释得更好。发送到 GUI 的进度包括我在地图上显示的地理坐标。一个接一个地显示每个进度在地图上提供某种动画。这就是为什么我不想跳过其中任何一个的原因。
事实上,我不介意我在另一个线程中执行的方法是否在动画之前完成。我想要的只是确保所有点至少显示一定时间(比如 200 毫秒)。
听起来如果这是结果,那么将进程 运行 放在单独的线程中的全部意义就被浪费了。因此,我的第一个建议是尽可能减少更新次数。
如果这是不可能的,也许您可以修改作为每次更新的一部分发送的数据。用于报告的对象或数据结构有多大,有多复杂?能否通过降低复杂性来提高性能?
最后,您可以尝试另一种方法:如果您创建一个 third 线程来处理报告,并以更大的块将其传送到您的 GUI 会怎么样?如果你让你的工作线程向这个报告者线程报告它的状态,那么让报告者线程只偶尔向你的主 GUI 线程报告一次(例如,每 10 个中有 1 个,正如你在上面建议的那样,然后报告 10 个块数据),那么您就不会经常调用您的 GUI,但您仍然能够保留处理过程中的所有状态数据,并使其在 GUI 中可用。
我不知道这对你的特定情况有多可行,但它可能值得一两次试验?
我对你的解决方案有很多疑虑,但如果没有代码示例,我不能确定哪一个可能是问题。
首先,Stephen Cleary 在他的 StartNew
is Dangerous 文章中指出了使用默认参数使用此方法的真正问题:
Easy enough for the simple case, but let’s consider a more realistic example:
private void Form1_Load(object sender, EventArgs e)
{
Compute(3);
}
private void Compute(int counter)
{
// If we're done computing, just return.
if (counter == 0)
return;
var ui = TaskScheduler.FromCurrentSynchronizationContext();
Task.Factory.StartNew(() => A(counter))
.ContinueWith(t =>
{
Text = t.Result.ToString(); // Update UI with results.
// Continue working.
Compute(counter - 1);
}, ui);
}
private int A(int value)
{
return value; // CPU-intensive work.
}
...
Now, the question returns: what thread does A
run on? Go ahead and walk through it; you should have enough knowledge at this point to figure out the answer.
Ready? The method A
runs on a thread pool thread the first time, and then it runs on the UI
thread the last two times.
我强烈建议您阅读整篇文章以更好地理解 StartNew
方法的用法,但想指出那里的最后建议:
Unfortunately, the only overloads for StartNew
that take a
TaskScheduler
also require you to specify the CancellationToken
and
TaskCreationOptions
. This means that in order to use
Task.Factory.StartNew
to reliably, predictably queue work to the
thread pool, you have to use an overload like this:
Task.Factory.StartNew(A, CancellationToken.None,
TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
And really, that’s kind of ridiculous. Just use Task.Run(() => A());
.
因此,您的代码可能可以通过切换创建新闻任务的方法轻松改进。但是对于你的问题还有一些其他的建议:
- 使用
BlockingCollection
来存储报告,并从这个队列中写入一个简单的消费者到 UI,所以你总是有有限数量的报告来表示,但最后所有这些都将得到处理。
- 使用
ConcurrentExclusiveSchedulerPair
class for your logic: for generating the reports use the ConcurrentScheduler
Property and for displaying them use ExclusiveScheduler
Property.
假设我有一个方法,我通过 Task.Factory.StartNew()
在单独的线程中 运行。
此方法报告的进度如此之多 (IProgress
),以至于它冻结了我的 GUI。
我知道简单地减少报告数量是一种解决方案,比如只报告十分之一,但就我而言,我真的想获取所有报告并将它们显示在我的 GUI 中。
我的第一个想法是将所有报告排队并逐一处理,每份报告之间稍作停顿。
第一:这是一个好的选择吗?
其次:如何实现?使用计时器或使用某种 Task.Delay()
?
更新:
我会尽力解释得更好。发送到 GUI 的进度包括我在地图上显示的地理坐标。一个接一个地显示每个进度在地图上提供某种动画。这就是为什么我不想跳过其中任何一个的原因。
事实上,我不介意我在另一个线程中执行的方法是否在动画之前完成。我想要的只是确保所有点至少显示一定时间(比如 200 毫秒)。
听起来如果这是结果,那么将进程 运行 放在单独的线程中的全部意义就被浪费了。因此,我的第一个建议是尽可能减少更新次数。
如果这是不可能的,也许您可以修改作为每次更新的一部分发送的数据。用于报告的对象或数据结构有多大,有多复杂?能否通过降低复杂性来提高性能?
最后,您可以尝试另一种方法:如果您创建一个 third 线程来处理报告,并以更大的块将其传送到您的 GUI 会怎么样?如果你让你的工作线程向这个报告者线程报告它的状态,那么让报告者线程只偶尔向你的主 GUI 线程报告一次(例如,每 10 个中有 1 个,正如你在上面建议的那样,然后报告 10 个块数据),那么您就不会经常调用您的 GUI,但您仍然能够保留处理过程中的所有状态数据,并使其在 GUI 中可用。
我不知道这对你的特定情况有多可行,但它可能值得一两次试验?
我对你的解决方案有很多疑虑,但如果没有代码示例,我不能确定哪一个可能是问题。
首先,Stephen Cleary 在他的 StartNew
is Dangerous 文章中指出了使用默认参数使用此方法的真正问题:
Easy enough for the simple case, but let’s consider a more realistic example:
private void Form1_Load(object sender, EventArgs e) { Compute(3); } private void Compute(int counter) { // If we're done computing, just return. if (counter == 0) return; var ui = TaskScheduler.FromCurrentSynchronizationContext(); Task.Factory.StartNew(() => A(counter)) .ContinueWith(t => { Text = t.Result.ToString(); // Update UI with results. // Continue working. Compute(counter - 1); }, ui); } private int A(int value) { return value; // CPU-intensive work. }
... Now, the question returns: what thread does
A
run on? Go ahead and walk through it; you should have enough knowledge at this point to figure out the answer.
Ready? The methodA
runs on a thread pool thread the first time, and then it runs on theUI
thread the last two times.
我强烈建议您阅读整篇文章以更好地理解 StartNew
方法的用法,但想指出那里的最后建议:
Unfortunately, the only overloads for
StartNew
that take aTaskScheduler
also require you to specify theCancellationToken
andTaskCreationOptions
. This means that in order to useTask.Factory.StartNew
to reliably, predictably queue work to the thread pool, you have to use an overload like this:Task.Factory.StartNew(A, CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
And really, that’s kind of ridiculous. Just use
Task.Run(() => A());
.
因此,您的代码可能可以通过切换创建新闻任务的方法轻松改进。但是对于你的问题还有一些其他的建议:
- 使用
BlockingCollection
来存储报告,并从这个队列中写入一个简单的消费者到 UI,所以你总是有有限数量的报告来表示,但最后所有这些都将得到处理。 - 使用
ConcurrentExclusiveSchedulerPair
class for your logic: for generating the reports use theConcurrentScheduler
Property and for displaying them useExclusiveScheduler
Property.