延迟执行的最佳方法

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());.

因此,您的代码可能可以通过切换创建新闻任务的方法轻松改进。但是对于你的问题还有一些其他的建议:

  1. 使用 BlockingCollection 来存储报告,并从这个队列中写入一个简单的消费者到 UI,所以你总是有有限数量的报告来表示,但最后所有这些都将得到处理。
  2. 使用 ConcurrentExclusiveSchedulerPair class for your logic: for generating the reports use the ConcurrentScheduler Property and for displaying them use ExclusiveScheduler Property.