c# TaskFactory ContinueWhenAll unexpectedly running before all tasks complete

妖精的绣舞 提交于 2020-05-31 04:54:05

问题


I have a data processing program in C# (.NET 4.6.2; WinForms for the UI). I'm experiencing a strange situation where computer speed seems to be causing Task.Factory.ContinueWhenAll to run earlier than expected or some Tasks are reporting complete before actually running. As you can see below, I have a queue of up to 390 tasks, with no more than 4 in queue at once. When all tasks are complete, the status label is updated to say complete. The ScoreManager involves retrieving information from a database, performing several client-side calculations, and saving to an Excel file.

When running the program from my laptop, everything functions as expected; when running from a substantially more powerful workstation, I experience this issue. Unfortunately, due to organizational limitations, I likely cannot get Visual Studio on the workstation to debug directly. Does anyone have any idea what might be causing this for me to investigate?

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);
}

回答1:


You are creating fire-and-forget tasks, that you don't wait or observe, here:

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

The ContinueWith method returns a Task. You probably need to attach the allComplete continuation to these tasks, instead of their antecedents:

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

As a side note, you could make your code half in size and significantly more readable if you used async/await instead of the old-school ContinueWith and Invoke((MethodInvoker) technique.


Also: setting an upper limit to the number of ThreadPool threads in order to control the degree of parallelism is extremely inadvisable:

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

You can use the Parallel class instead. It allows controlling the MaxDegreeOfParallelism quite easily.




回答2:


After discovering state was IsFaulted, I added some code to add some exception information to the log (https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library). Seems the problem is an underlying database issue where there are not enough connections left in the connection pool (Timeout expired. The timeout period elapsed prior to obtaining a connection from the pool. This may have occurred because all pooled connections were in use and max pool size was reached.)--the additional speed allows queries to fire more quickly/frequently. Not sure entirely why, as I do have the SqlConnection enclosed in a using clause, but investigating a few things on that front. At any rate, the problem is clearly a little different than what I thought above, so marking this quasi-answered.



来源:https://stackoverflow.com/questions/61808194/c-sharp-taskfactory-continuewhenall-unexpectedly-running-before-all-tasks-comple

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!