使用异步代码时的具体死锁场景
Specific deadlock scenario when using asynchronous code
我已经阅读了数小时有关基于任务的异步模式的内容。
现在我试图证明我自己理解正确,所以我强迫自己进行以下练习:
在 WebApi 控制器中,等待非 async
控制器中的 async
操作,然后尝试访问 ControllerContext
。
但事情并没有像我想象的那样发展。
编辑:代码具有误导性,请不要考虑...
//using System.Diagnostics;
//using System.Threading.Tasks;
//using System.Web.Http;
//namespace DeadlockTest.Controllers
//{
// public class ValuesController : ApiController
// {
// public string Get()
// {
// var task = DoSomething();
// //task.Wait();
// task.ConfigureAwait(false).GetAwaiter().GetResult();
// return ControllerContext?.ToString(); // not reached
// }
// private async Task DoSomething()
// {
// await Task.Delay(new System.TimeSpan(0, 0, 0, 0, 100));
// Debugger.Break(); // not reached
// }
// }
//}
我很惊讶Debugger.Break();
没有达到!
即使我只是打电话task.Wait();
。
task.ConfigureAwait(false).GetAwaiter().GetResult();
有点尴尬,它只是随机拍摄,能够等待不应该 "block" 当前 SynchronizationContext 的任务。
如何解决这个问题,知道方法签名不能改变*(为了让这个练习对我有意义)?
- 因此没有
public async string Get()
挽回局面
编辑:
public void LogGet()
{
Action get = Get;
var logger = new RussianOpCodeLibrary.ActionLogger(get);
logger.Dosvidaniya();
}
private void Get()
{
DoSomething().Wait();
}
private async Task DoSomething()
{
await Task.Delay(new System.TimeSpan(0, 0, 0, 0, 100));
Debugger.Break(); // not reached
}
Get()
不是重载,它只是响应 GET
请求而调用的方法,因为 Web API 的约定和路由配置。如果你想让它使用await
,只需更改它的签名:
public class ValuesController : ApiController
{
public async Task<string> Get()
{
await DoSomething();
return ControllerContext?.ToString();
}
Task DoSomething()=> Task.Delay(new System.TimeSpan(0, 0, 0, 0, 100));
}
这就是使其异步所需的全部内容。
你的代码解除锁定是因为你在 await Task.Delay(new System.TimeSpan(0, 0, 0, 0, 100));
有机会 return .在原始同步上下文冻结的情况下,await
不能 return。
对 task.ConfigureAwait(false)
的调用不会执行任何操作,因为您没有在任何地方等待该任务。您立即阻止了它。如果使用 with await Task.Delay()
,即 await Task.Delay().ConfigureAwait(false);
,则可以避免死锁,但这只是首先掩盖了阻塞问题
我想我可以使用委派任务来解决这个问题。
public string Get()
{
Task.Factory.StartNew(() =>
{
var task = DoSomething();
task.Wait();
}).Wait();
Task.Factory.StartNew(async () =>
{
var task = DoSomething();
await task;
}).Unwrap().Wait();
//task.ConfigureAwait(false).GetAwaiter().GetResult();
return ControllerContext?.ToString();
}
How can this be solved, knowing the methods signature must not change* (for this exercice to bear any meaning to me)?
Sometimes in life you don't have full control over the whole codebase and there are signatures you have to accept and deal with.
你问的是如何在同步代码中使用异步代码。对于这个问题,示例代码是一个糟糕的选择,因为在示例 ASP.NET 控制器代码中,您 do 可以控制您的方法签名。
简而言之,在同步代码中使用异步代码是个坏主意。当您探索这一点时,您会发现有各种各样的 hack,但其中 none 在每种情况下都能正常工作。例如,the "blocking hack" can easily cause deadlock 当存在 SynchronizationContext
时,如您所见。
你应该永远努力使用async
all the way. Seriously, the best answer to "how do I do this" is "you don't!" If you think about it for a bit, this makes sense; synchronous code (by definition) blocks a thread, and asynchronous code (by definition) does not. So, your asynchronous code is going through all the trouble to be asynchronous, only to be called by blocking code, which completely removes all the benefits of asynchronous code in the first place! It's better to go fully-async (or fully-sync). This is why 是好的;这是您的示例代码的最佳答案。
但是,在少数情况下无法避免异步同步。在这些情况下,您需要采用我在 brownfield async
.
上的文章中详述的技巧之一
我已经阅读了数小时有关基于任务的异步模式的内容。 现在我试图证明我自己理解正确,所以我强迫自己进行以下练习:
在 WebApi 控制器中,等待非 async
控制器中的 async
操作,然后尝试访问 ControllerContext
。
但事情并没有像我想象的那样发展。
编辑:代码具有误导性,请不要考虑...
//using System.Diagnostics;
//using System.Threading.Tasks;
//using System.Web.Http;
//namespace DeadlockTest.Controllers
//{
// public class ValuesController : ApiController
// {
// public string Get()
// {
// var task = DoSomething();
// //task.Wait();
// task.ConfigureAwait(false).GetAwaiter().GetResult();
// return ControllerContext?.ToString(); // not reached
// }
// private async Task DoSomething()
// {
// await Task.Delay(new System.TimeSpan(0, 0, 0, 0, 100));
// Debugger.Break(); // not reached
// }
// }
//}
我很惊讶Debugger.Break();
没有达到!
即使我只是打电话task.Wait();
。
task.ConfigureAwait(false).GetAwaiter().GetResult();
有点尴尬,它只是随机拍摄,能够等待不应该 "block" 当前 SynchronizationContext 的任务。
如何解决这个问题,知道方法签名不能改变*(为了让这个练习对我有意义)?
- 因此没有
public async string Get()
挽回局面
编辑:
public void LogGet()
{
Action get = Get;
var logger = new RussianOpCodeLibrary.ActionLogger(get);
logger.Dosvidaniya();
}
private void Get()
{
DoSomething().Wait();
}
private async Task DoSomething()
{
await Task.Delay(new System.TimeSpan(0, 0, 0, 0, 100));
Debugger.Break(); // not reached
}
Get()
不是重载,它只是响应 GET
请求而调用的方法,因为 Web API 的约定和路由配置。如果你想让它使用await
,只需更改它的签名:
public class ValuesController : ApiController
{
public async Task<string> Get()
{
await DoSomething();
return ControllerContext?.ToString();
}
Task DoSomething()=> Task.Delay(new System.TimeSpan(0, 0, 0, 0, 100));
}
这就是使其异步所需的全部内容。
你的代码解除锁定是因为你在 await Task.Delay(new System.TimeSpan(0, 0, 0, 0, 100));
有机会 return .在原始同步上下文冻结的情况下,await
不能 return。
对 task.ConfigureAwait(false)
的调用不会执行任何操作,因为您没有在任何地方等待该任务。您立即阻止了它。如果使用 with await Task.Delay()
,即 await Task.Delay().ConfigureAwait(false);
,则可以避免死锁,但这只是首先掩盖了阻塞问题
我想我可以使用委派任务来解决这个问题。
public string Get()
{
Task.Factory.StartNew(() =>
{
var task = DoSomething();
task.Wait();
}).Wait();
Task.Factory.StartNew(async () =>
{
var task = DoSomething();
await task;
}).Unwrap().Wait();
//task.ConfigureAwait(false).GetAwaiter().GetResult();
return ControllerContext?.ToString();
}
How can this be solved, knowing the methods signature must not change* (for this exercice to bear any meaning to me)?
Sometimes in life you don't have full control over the whole codebase and there are signatures you have to accept and deal with.
你问的是如何在同步代码中使用异步代码。对于这个问题,示例代码是一个糟糕的选择,因为在示例 ASP.NET 控制器代码中,您 do 可以控制您的方法签名。
简而言之,在同步代码中使用异步代码是个坏主意。当您探索这一点时,您会发现有各种各样的 hack,但其中 none 在每种情况下都能正常工作。例如,the "blocking hack" can easily cause deadlock 当存在 SynchronizationContext
时,如您所见。
你应该永远努力使用async
all the way. Seriously, the best answer to "how do I do this" is "you don't!" If you think about it for a bit, this makes sense; synchronous code (by definition) blocks a thread, and asynchronous code (by definition) does not. So, your asynchronous code is going through all the trouble to be asynchronous, only to be called by blocking code, which completely removes all the benefits of asynchronous code in the first place! It's better to go fully-async (or fully-sync). This is why
但是,在少数情况下无法避免异步同步。在这些情况下,您需要采用我在 brownfield async
.