【发布时间】:2018-03-11 14:16:45
【问题描述】:
考虑以下我将同步等待的异步方法。等一下,我知道。我知道这被认为是不好的做法和causes deadlocks,但我完全同意conscious,并采取措施通过使用Task.Run 包装代码来防止死锁。
private async Task<string> BadAssAsync()
{
HttpClient client = new HttpClient();
WriteInfo("BEFORE AWAIT");
var response = await client.GetAsync("http://google.com");
WriteInfo("AFTER AWAIT");
string content = await response.Content.ReadAsStringAsync();
WriteInfo("AFTER SECOND AWAIT");
return content;
}
如果这样调用:BadAssAsync().Result,则此代码肯定会死锁(在具有 SyncronizationContext 的环境中,它在 ASP.NET 等单线程上安排任务)。
我面临的问题是,即使使用这个“安全”包装器,它仍然偶尔会死锁。
private T Wait1<T>(Func<Task<T>> taskGen)
{
return Task.Run(() =>
{
WriteInfo("RUN");
var task = taskGen();
return task.Result;
}).Result;
}
这些“WriteInfo”行是有目的的。这些调试行让我看到它偶尔发生的原因是Task.Run 中的代码,有点神秘,是由开始服务请求的同一个线程执行的。这意味着它的 AspNetSynchronizationContext 为SyncronizationContext 并且肯定会死锁。
这里是调试输出:
***(工作正常) 开始:TID:17; SCTX:System.Web.AspNetSynchronizationContext;调度器:System.Threading.Tasks.ThreadPoolTaskScheduler 运行:TID:45; SCTX:<null> 调度器:System.Threading.Tasks.ThreadPoolTaskScheduler 等待之前:TID:45; SCTX:<null> 调度器:System.Threading.Tasks.ThreadPoolTaskScheduler 等待后:TID:37; SCTX:<null> 调度器:System.Threading.Tasks.ThreadPoolTaskScheduler 第二次等待后:TID:37; SCTX:<null> 调度器:System.Threading.Tasks.ThreadPoolTaskScheduler ***(死锁) 开始:TID:48; SCTX:System.Web.AspNetSynchronizationContext;调度器:System.Threading.Tasks.ThreadPoolTaskScheduler 运行:TID:48; SCTX:System.Web.AspNetSynchronizationContext;调度器:System.Threading.Tasks.ThreadPoolTaskScheduler 等待之前:TID:48; SCTX:System.Web.AspNetSynchronizationContext;调度器:System.Threading.Tasks.ThreadPoolTaskScheduler
注意Task.Run() 中的代码在 TID=48 的同一线程上继续。
问题是为什么会这样?为什么 Task.Run 在同一个线程上运行代码允许 SyncronizationContext 仍然有效?
这里是WebAPI控制器的完整示例代码:https://pastebin.com/44RP34Ye和完整示例代码here。
更新。这是重现问题根本原因的较短控制台应用程序代码示例 - 在等待的调用线程上调度 Task.Run 委托。这怎么可能?
static void Main(string[] args)
{
WriteInfo("\n***\nBASE");
var t1 = Task.Run(() =>
{
WriteInfo("T1");
Task t2 = Task.Run(() =>
{
WriteInfo("T2");
});
t2.Wait();
});
t1.Wait();
}
基础:TID:1; SCTX:<null> 调度器:System.Threading.Tasks.ThreadPoolTaskScheduler T1:TID:3; SCTX:<null> 调度器:System.Threading.Tasks.ThreadPoolTaskScheduler T2:TID:3; SCTX: <null> 调度器: System.Threading.Tasks.ThreadPoolTaskScheduler
【问题讨论】:
-
taking measures to prevent deadlocks via wrapping code with Task.Run.如您所见,这并不能解决您的问题。您不应该同步等待异步操作。它本质上是有问题的。没有任何“简单的技巧”可以让它变得好起来。如果您不想不断处理此类问题,则需要消除该潜在问题,并使您的代码完全异步或完全同步。 -
@NeilBostian:“任务”本身不会“运行”,
Task.Run除外,它确实在后台线程上运行。您正在考虑调用异步方法。 -
@NeilBostian
Task是异步工作的代表。根据您使用它们的方式以及它们的实现方式,它们最终可能会或可能不会并行完成这项工作。 “任务将始终在产生它的同一线程上运行”这句话甚至没有意义。许多任务根本不涉及在线程中运行代码,而那些通常不涉及在调用线程中运行代码(因为它们应该是异步的)。您的链接答案没有正确使用其术语,因为它声称在并行完成工作时没有并行完成工作。 -
@SLaks,“你的方法根本没有帮助” - 感谢您注意到这一点!问题是为什么?
-
好问题! @All:问题不在于“良好实践”,而在于 TPL 的内部运作。
标签: c# .net multithreading asynchronous deadlock