首页 > 解决方案 > c# TaskFactory ContinueWhenAll 在所有任务完成之前意外运行

问题描述

我有一个 C# 中的数据处理程序(.NET 4.6.2;用于 UI 的 WinForms)。我遇到了一种奇怪的情况,计算机速度似乎导致 Task.Factory.ContinueWhenAll 比预期更早地运行,或者某些任务在实际运行之前报告完成。正如你在下面看到的,我有一个最多 390 个任务的队列,一次队列中不超过 4 个。当所有任务都完成时,状态标签会更新为完成。ScoreManager 涉及从数据库中检索信息、执行多个客户端计算以及保存到 Excel 文件。

从我的笔记本电脑运行程序时,一切都按预期运行;从功能更强大的工作站运行时,我遇到了这个问题。不幸的是,由于组织限制,我可能无法在工作站上直接使用 Visual Studio 进行调试。有谁知道可能导致我调查的原因是什么?

private void button1_Click(object sender, EventArgs e)
{
    int startingIndex = cbStarting.SelectedIndex;
    int endingIndex = cbEnding.SelectedIndex;
    lblStatus.Text = "Running";
    if (endingIndex < startingIndex)
    {
        MessageBox.Show("Ending must be further down the list than starting.");
        return;
    }
    List<string> lItems = new List<string>();
    for (int i = startingIndex; i <= endingIndex; i++)
    {
        lItems.Add(cbStarting.Items[i].ToString());
    }

    System.IO.Directory.CreateDirectory(cbMonth.SelectedItem.ToString());

    ThreadPool.SetMaxThreads(4, 4);
    List<Task<ScoreResult>> tasks = new List<Task<ScoreResult>>();
    for (int i = startingIndex; i <= endingIndex; i++)
    {
        ScoreManager sm = new ScoreManager(cbStarting.Items[i].ToString(),
            cbMonth.SelectedItem.ToString());
        Task<ScoreResult> task = Task.Factory.StartNew<ScoreResult>((manager) =>
            ((ScoreManager)manager).Execute(), sm);
        sm = null;
        Action<Task<ScoreResult>> itemcomplete = ((_task) =>
        {
            if (_task.Result.errors.Count > 0)
            {
                txtLog.Invoke((MethodInvoker)delegate
                {
                    txtLog.AppendText("Item " + _task.Result.itemdetail +
                        " had errors/warnings:" + Environment.NewLine);
                });

                foreach (ErrorMessage error in _task.Result.errors)
                {
                    txtLog.Invoke((MethodInvoker)delegate
                    {
                        txtLog.AppendText("\t" + error.ErrorText +
                            Environment.NewLine);
                    });
                }
            }
            else
            {
                txtLog.Invoke((MethodInvoker)delegate
                {
                    txtLog.AppendText("Item " + _task.Result.itemdetail +
                     " succeeded." + Environment.NewLine);
                });

            }
        });
        task.ContinueWith(itemcomplete);
        tasks.Add(task);
    }
    Action<Task[]> allComplete = ((_tasks) =>
    {
        lblStatus.Invoke((MethodInvoker)delegate
        {
            lblStatus.Text = "Complete";
        });
    });
    Task.Factory.ContinueWhenAll<ScoreResult>(tasks.ToArray(), allComplete);
}

标签: c#multithreadingconcurrencytask

解决方案


您正在这里创建无需等待或观察的即发即弃的任务:

task.ContinueWith(itemcomplete);
tasks.Add(task);
Task.Factory.ContinueWhenAll<ScoreResult>(tasks.ToArray(), allComplete);

ContinueWith方法返回一个Task. 您可能需要将allComplete延续附加到这些任务,而不是它们的前身:

List<Task> continuations = new List<Task>();
Task continuation = task.ContinueWith(itemcomplete);
continuations.Add(continuation);
Task.Factory.ContinueWhenAll<ScoreResult>(continuations.ToArray(), allComplete);

附带说明一下,如果您使用async/await而不是老式的ContinueWith技术,您可以使您的代码大小减半并显着提高可读性Invoke((MethodInvoker)


另外:ThreadPool为了控制并行度而设置线程数的上限是非常不可取的:

ThreadPool.SetMaxThreads(4, 4); // Don't do this!

您可以改用Parallel该类。它可以MaxDegreeOfParallelism很容易地控制。


推荐阅读