Dre*_*mer 6 .net c# multithreading task-parallel-library async-await
环境: Windows Server 2012,.net 4.5,visual studio 2013,
注意:不是UI应用程序(因此与着名的async/await/synchronizationcontext问题无关)(参考:http://channel9.msdn.com/Series/Three-Essential-Tips-for-Async/Async-library-methods-should -consider-using-Task-ConfigureAwait-false-)
编辑
其实我的不好 - 这是TYPO导致了交易.我粘贴了下面的样本片段(伪),导致死锁.基本上,我没有基于'childtasks'进行组合,而是在'外部任务'上做了:(.看起来我不应该在看电视时写'异步'代码:).
我已经离开原始代码片段,因为它确实回答了我的第一个问题(与我之前的两个代码片段中的async/await和unwrap的区别).死锁让我分心看到实际的问题:).谢谢大家的评论.
static void Main(string[] args)
{
Task t = IndefinitelyBlockingTask();
t.Wait();
}
static Task IndefinitelyBlockingTask()
{
List<Task> tasks = new List<Task>();
Task task = FooAsync();
tasks.Add(task);
Task<Task> continuationTask = task.ContinueWith(t =>
{
Task.Delay(10000);
List<Task> childtasks = new List<Task>();
////get child tasks
//now INSTEAD OF ADDING CHILD TASKS, i added outer method TASKS. Typo :(:)!
Task wa = Task.WhenAll(tasks/*TYPO*/);
return wa;
}, TaskContinuationOptions.OnlyOnRanToCompletion);
tasks.Add(continuationTask);
Task unwrappedTask = continuationTask.Unwrap();
tasks.Add(unwrappedTask);
Task whenall = Task.WhenAll(tasks.ToArray());
return whenall;
}
Run Code Online (Sandbox Code Playgroud)
代码片段在"解包"延续任务被无限期等待时被添加到我已经粘贴在伪(我的实际应用中的模式/成语块 - 而不是样本)下面的任务的聚合/链中,代码片段(#1)无限期地等待'unwrapped'任务已添加到列表中.VS Debugger的'Debugger + windows + threads'窗口显示该线程只是在ManualResetEventSlim.Wait上阻塞.
代码片段与async/await一起使用,并删除未解包的任务然后我删除(在调试时随机),这个unwrap语句并在lambda中使用async/await(请参见下文).令人惊讶的是它有效.但我不确定为什么:(?.
问题
不使用unwrap和async/await在下面的代码片段中用于相同的目的吗?我最初只是首选片段#1,因为我只是想避免生成过多的代码,因为调试器不是那么友好(特别是在异常通过链式任务传播的错误情况下 - 异常中的callstack显示movenext而不是我的实际代码) .如果是,那么这是TPL中的错误吗?
我错过了什么?如果它们相同,哪种方法更受欢迎?
关于调试器+任务窗口 '调试器+任务'窗口的注意事项没有显示任何细节(注意它在我的环境中工作不正确(至少我的理解),因为它从不显示未安排的任务并且显示活动任务)
无限期等待ManualResetEventSlim.Wait的代码片段
static Task IndefinitelyBlockingTask()
{
List<Task> tasks = new List<Task>();
Task task = FooAsync();
tasks.Add(task);
Task<Task> continuationTask = task.ContinueWith(t =>
{
List<Task> childTasks = new List<Task>();
for (int i = 1; i <= 5; i++)
{
var ct = FooAsync();
childTasks.Add(ct);
}
Task wa = Task.WhenAll(childTasks.ToArray());
return wa;
}, TaskContinuationOptions.OnlyOnRanToCompletion);
tasks.Add(continuationTask);
Task unwrappedTask = continuationTask.Unwrap();
//commenting below code and using async/await in lambda works (please see below code snippet)
tasks.Add(unwrappedTask);
Task whenall = Task.WhenAll(tasks.ToArray());
return whenall;
}
Run Code Online (Sandbox Code Playgroud)
代码片段在lambda中使用async/await而不是unwrap
static Task TaskWhichWorks()
{
List<Task> tasks = new List<Task>();
Task task = FooAsync();
tasks.Add(task);
Task<Task> continuationTask = task.ContinueWith(async t =>
{
List<Task> childTasks = new List<Task>();
for (int i = 1; i <= 5; i++)
{
var ct = FooAsync();
childTasks.Add(ct);
}
Task wa = Task.WhenAll(childTasks.ToArray());
await wa.ConfigureAwait(continueOnCapturedContext: false);
}, TaskContinuationOptions.OnlyOnRanToCompletion);
tasks.Add(continuationTask);
Task whenall = Task.WhenAll(tasks.ToArray());
return whenall;
}
Run Code Online (Sandbox Code Playgroud)
显示阻塞代码的Callstack
mscorlib.dll!System.Threading.ManualResetEventSlim.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Unknown
mscorlib.dll!System.Threading.Tasks.Task.SpinThenBlockingWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Unknown
mscorlib.dll!System.Threading.Tasks.Task.InternalWait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Unknown
mscorlib.dll!System.Threading.Tasks.Task.Wait(int millisecondsTimeout, System.Threading.CancellationToken cancellationToken) Unknown
mscorlib.dll!System.Threading.Tasks.Task.Wait() Unknown
Run Code Online (Sandbox Code Playgroud)
最好的祝福
Kir*_*kiy 10
好吧,让我们试着深入了解这里发生的事情.
首先要做的事情ContinueWith是:传递给你的lambda的差别是微不足道的:功能上这两部分的例子是相同的(至少就我所见).
这是FooAsync我用于测试的实现:
static Task FooAsync()
{
return Task.Delay(500);
}
Run Code Online (Sandbox Code Playgroud)
我发现很奇怪的是,使用这个实现你IndefinitelyBlockingTask需要花费两倍的时间TaskWhichWorks(分别为1秒和500毫秒).显然行为已经改变了Unwrap.
有敏锐眼光的人可能会马上发现这个问题,但我个人并没有使用任务延续或 Unwrap那么多,所以花了一点时间沉入其中.
这里是踢球者:除非你Unwrap在两种情况下使用延续,否则预定的任务将ContinueWith同步完成(并立即 - 无论循环内创建的任务有多长时间).在lambda中创建的任务(Task.WhenAll(childTasks.ToArray())让我们称之为内部任务)以一种即发即忘的方式进行调度,并在运行时进行观察.
Unwrapping返回的任务ContinueWith意味着内部任务不再是发射 - 忘记 - 它现在是执行链的一部分,当你将它添加到列表中时,外部任务(Task.WhenAll(tasks.ToArray()))无法完成,直到内部任务完成).
使用ContinueWith(async () => { })不会改变上述行为,因为async lambda返回的任务不是自动解包的(想想
// These two have similar behaviour and
// are interchangeable for our purposes.
Task.Run(() => Task.Delay(500))
Task.Run(async () => await Task.Delay(500));
Run Code Online (Sandbox Code Playgroud)
VS
Task.Factory.StartNew(() => Task.Delay(500))
Run Code Online (Sandbox Code Playgroud)
该Task.Run调用已Unwrap内置(请参阅http://referencesource.microsoft.com/#mscorlib/system/threading/Tasks/Task.cs#0fb2b4d9262599b9#references); 该StartNew呼叫不和它返回的任务只是立即完成,而无需等待内部任务.在这方面ContinueWith类似StartNew.
边注
重现在使用时观察到的行为的另一种方法Unwrap是确保在循环(或其继续)内创建的任务附加到父级,导致父任务(创建者ContinueWith)不转换到完成状态,直到所有子任务都有完了.
for (int i = 1; i <= 5; i++)
{
var ct = FooAsync().ContinueWith(_ => { }, TaskContinuationOptions.AttachedToParent);
childTasks.Add(ct);
}
Run Code Online (Sandbox Code Playgroud)
回到原来的问题
在您当前的实现中,即使您具有await Task.WhenAll(tasks.ToArray())外部方法的最后一行,该方法仍将在lambda 内创建的任务完成之前返回ContinueWith.即使内部创建的任务ContinueWith永远不会完成(我的猜测就是你的生产代码中发生了什么),外部方法仍然会返回正常.
所以,上述代码所有意外的事情都是由愚蠢造成的,ContinueWith除非你使用,否则它本质上是"落伍" Unwrap.async/ await决不原因或治愈(虽然,不可否认,它可以而且也应该用来重写你的方法以更合理的方式-延续是很难与导致问题要解决,如这一个).
那么生产中发生了什么
以上所有这些让我相信,在你的ContinueWithlambda中旋转的任务之一中存在一个死锁,导致Task.WhenAll在生产修剪中永远无法完成.
不幸的是,你还没有发布一个简洁的问题重复(我想我可以为你做这些信息,但这不是我的工作),甚至生产代码,所以这是一个很大的解决方案我可以给.
您没有使用伪代码观察所描述的行为这一事实应该暗示您可能最终剥离导致问题的位.如果你认为这听起来很愚蠢,那是因为它是,这就是为什么我最终收回我原来的问题,尽管它是我偶然遇到的最奇怪的异常问题.
结论:看看你的ContinueWithlambda.
最后编辑
你坚持Unwrap并await做同样的事情,这是真的(不是因为它最终会混淆任务组合,而是真实的 - 至少为了这个例子的目的).但是,话虽如此,你从来没有完全重新使用Unwrap语义await,所以这个方法的行为有什么不同呢?下面是TaskWhichWorks与await将类似的行为的Unwrap例子(这也是容易锁死的问题时,应用到你的产品代码):
static async Task TaskWhichUsedToWorkButNotAnymore()
{
List<Task> tasks = new List<Task>();
Task task = FooAsync();
tasks.Add(task);
Task<Task> continuationTask = task.ContinueWith(async t =>
{
List<Task> childTasks = new List<Task>();
for (int i = 1; i <= 5; i++)
{
var ct = FooAsync();
childTasks.Add(ct);
}
Task wa = Task.WhenAll(childTasks.ToArray());
await wa.ConfigureAwait(continueOnCapturedContext: false);
}, TaskContinuationOptions.OnlyOnRanToCompletion);
tasks.Add(continuationTask);
// Let's Unwrap the async/await way.
// Pay attention to the return type.
// The resulting task represents the
// completion of the task started inside
// (and returned by) the ContinueWith delegate.
// Without this you have no reference, and no
// way of waiting for, the inner task.
Task unwrappedTask = await continuationTask;
// Boom! This method now has the
// same behaviour as the other one.
tasks.Add(unwrappedTask);
await Task.WhenAll(tasks.ToArray());
// Another way of "unwrapping" the
// continuation just to drive the point home.
// This will complete immediately as the
// continuation task as well as the task
// started inside, and returned by the continuation
// task, have both completed at this point.
await await continuationTask;
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
2553 次 |
| 最近记录: |