c# 任务工厂继续时间所有任务在所有任务完成之前意外运行



我有一个C#的数据处理程序(.NET 4.6.2;用于用户界面的 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);
}

您正在创建即发即弃的任务,您不会等待或观察,在这里:

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 而不是老式的ContinueWithInvoke((MethodInvoker)技术,则可以使代码大小减半,并且更具可读性。


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

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

您可以改用Parallel类。它允许非常轻松地控制MaxDegreeOfParallelism

在发现状态为 IsFaulted 后,我添加了一些代码以将一些异常信息添加到日志 (https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/exception-handling-task-parallel-library(。似乎问题是一个基础数据库问题,其中连接池中没有足够的连接(超时已过期。 从池获取连接之前经过的超时期限。 这可能是因为所有池连接都在使用中,并且已达到最大池大小。--额外的速度允许查询更快/更频繁地触发。不知道完全为什么,因为我确实将 SqlConnection 包含在 using 子句中,但在这方面调查了一些事情。无论如何,这个问题显然与我上面的想法略有不同,所以标记这个准答案。

最新更新