我有一个稍微复杂的要求,即并行执行一些任务,并且必须等待其中一些任务完成才能继续。现在,当我有许多任务要并行执行时,我遇到了意外行为,但在 ContinueWith 处理程序中。我准备了一个小样本来说明问题:
var task1 = Task.Factory.StartNew(() =>
{
Console.WriteLine("11");
Thread.Sleep(1000);
Console.WriteLine("12");
}).ContinueWith(async t =>
{
Console.WriteLine("13");
var innerTasks = new List<Task>();
for (var i = 0; i < 10; i++)
{
var j = i;
innerTasks.Add(Task.Factory.StartNew(() =>
{
Console.WriteLine("1_" + j + "_1");
Thread.Sleep(500);
Console.WriteLine("1_" + j + "_2");
}));
}
await Task.WhenAll(innerTasks.ToArray());
//Task.WaitAll(innerTasks.ToArray());
Thread.Sleep(1000);
Console.WriteLine("14");
});
var task2 = Task.Factory.StartNew(() =>
{
Console.WriteLine("21");
Thread.Sleep(1000);
Console.WriteLine("22");
}).ContinueWith(t =>
{
Console.WriteLine("23");
Thread.Sleep(1000);
Console.WriteLine("24");
});
Console.WriteLine("1");
await Task.WhenAll(task1, task2);
Console.WriteLine("2");
基本模式是: - 任务 1 应与任务 2 并行执行。 - 完成第 1 部分的第一部分后,它应该并行执行更多操作。我想完成,一旦一切都完成了。
我希望得到以下结果:
1 <- Start
11 / 21 <- The initial task start
12 / 22 <- The initial task end
13 / 23 <- The continuation task start
Some combinations of "1_[0..9]_[1..2]" and 24 <- the "inner" tasks of task 1 + the continuation of task 2 end
14 <- The end of the task 1 continuation
2 <- The end
相反,await Task.WhenAll(innerTasks.ToArray());
不会“阻止”继续任务完成。因此,内部任务在外部 await Task.WhenAll(task1, task2);
完成后执行。结果是这样的:
1 <- Start
11 / 21 <- The initial task start
12 / 22 <- The initial task end
13 / 23 <- The continuation task start
Some combinations of "1_[0..9]_[1..2]" and 24 <- the "inner" tasks of task 1 + the continuation of task 2 end
2 <- The end
Some more combinations of "1_[0..9]_[1..2]" <- the "inner" tasks of task 1
14 <- The end of the task 1 continuation
相反,如果我使用 Task.WaitAll(innerTasks.ToArray())
,一切似乎都按预期工作。当然,我不想使用 WaitAll,所以我不会阻塞任何线程。
我的问题是:
- 为什么会发生这种意外行为?
- 如何在不阻塞任何线程的情况下补救这种情况?
非常感谢您的指点!
最佳答案
您使用了错误的工具。代替 StartNew
,使用 Task.Run
。代替 ContinueWith
,使用 await
:
var task1 = Task1();
var task2 = Task2();
Console.WriteLine("1");
await Task.WhenAll(task1, task2);
Console.WriteLine("2");
private async Task Task1()
{
await Task.Run(() =>
{
Console.WriteLine("11");
Thread.Sleep(1000);
Console.WriteLine("12");
});
Console.WriteLine("13");
var innerTasks = new List<Task>();
for (var i = 0; i < 10; i++)
{
innerTasks.Add(Task.Run(() =>
{
Console.WriteLine("1_" + i + "_1");
Thread.Sleep(500);
Console.WriteLine("1_" + i + "_2");
}));
await Task.WhenAll(innerTasks);
}
Thread.Sleep(1000);
Console.WriteLine("14");
}
private async Task Task2()
{
await Task.Run(() =>
{
Console.WriteLine("21");
Thread.Sleep(1000);
Console.WriteLine("22");
});
Console.WriteLine("23");
Thread.Sleep(1000);
Console.WriteLine("24");
}
Task.Run
和 await
在这里更优越,因为它们纠正了 StartNew
/ContinueWith
中的许多意外行为>。特别是,异步委托(delegate)和(对于 Task.Run
)总是使用线程池。
我的博客上有关于 why you shouldn't use StartNew
的更多详细信息和 why you shouldn't use ContinueWith
.
关于c# - 在 ContinueWith block 内使用 await 的意外行为,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/40136943/