C# 使用异步代码时的特定死锁场景
关于基于任务的异步模式,我已经读了好几个小时了。 现在我试着证明自己我完全理解了它,所以我强迫自己做了以下练习: 在WebApi控制器内部,等待非C# 使用异步代码时的特定死锁场景,c#,asynchronous,deadlock,C#,Asynchronous,Deadlock,关于基于任务的异步模式,我已经读了好几个小时了。 现在我试着证明自己我完全理解了它,所以我强迫自己做了以下练习: 在WebApi控制器内部,等待非asyncone中的async操作,然后尝试访问ControllerContext。 但事情并不像我想象的那样发展 编辑:代码误导,不要考虑……/P> //using System.Diagnostics; //using System.Threading.Tasks; //using System.Web.Http; //namespace Dead
async
one中的async
操作,然后尝试访问ControllerContext
。
但事情并不像我想象的那样发展
<>编辑:代码误导,不要考虑……/P>
//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()
有点笨拙,它只是一个随机的镜头,可以等待一个不应该“阻止”当前SynchronizationContext的任务
知道方法签名不得更改*(此练习对我有任何意义)如何解决此问题
- 因此,没有
公共异步字符串Get()
来保存日期
编辑:
Get()
不是重载,它只是一个方法,由于Web API的约定和路由配置,它是响应Get
请求而调用的。如果希望它使用等待
,只需更改其签名:
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));
}
公共类值控制器:ApiController
{
公共异步任务Get()
{
等待做某事();
返回ControllerContext?.ToString();
}
Task DoSomething()=>Task.Delay(新系统时间跨度(0,0,0,0,100));
}
这就是使其异步所需要的全部
您的代码解除锁定,因为您在等待Task.Delay(新的System.TimeSpan(0,0,0,0,100))之前使用.GetResult()
冻结了原始线程/同步上下文代码>有机会返回。原始同步上下文冻结后,await
无法返回
调用task.ConfigureAwait(false)
不会执行任何操作,因为您不会在任何地方等待该任务。你马上就把它堵住了。如果与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();
}
知道方法签名不得更改*(此练习对我有任何意义)如何解决此问题
在生活中,有时你不能完全控制整个代码库,有些签名你必须接受和处理
您需要的是如何在同步代码中使用异步代码。对于这个问题,示例代码是一个糟糕的选择,因为在示例ASP.NET控制器代码中,您确实可以控制您的方法签名
简而言之,在同步代码中使用异步代码是个坏主意。当你探索这一点时,你会发现有各种各样的黑客,但是没有一种在每种情况下都能正常工作。例如,如您所发现的,当存在同步上下文时
你应该一直努力使用它。说真的,“我该怎么做”的最好答案是“你不需要!”如果你想一想,这是有道理的;同步代码(按定义)阻塞线程,异步代码(按定义)则不阻塞线程。因此,您的异步代码正经历着异步化的所有麻烦,只是被阻塞代码调用,这从一开始就完全消除了异步代码的所有好处!最好是完全异步(或完全同步)。这就是为什么是好的;这是您的示例代码的最佳答案
然而,在少数情况下,无法避免异步同步。在这些情况下,您需要采用我在上一篇文章中详细介绍的一种方法。Get()
是您的方法,而不是重载。将签名更改为async Task Get()
或async Task GetAsync()
。就异步操作而言,后缀并不重要。死锁是由您的代码引起的,因为如果您坚持不将签名更改为async Task Get()
您还不明白什么是async/await
,则调用.Wait
或.Result
(.GetResult()块)。如果您阻塞并且不使用wait
,则代码不是异步的。异步意味着您启动一个已经异步的操作,并等待它完成,而不阻塞当前线程。wait Task.Delay()。configurewait(false);如果DoSomething是我无法修改的第三方方法怎么办。你知道我如何在Get中调用而不使其异步(这是本练习要求的一部分)吗?你是说像HttpClientasync Task Get(){var client=new HttpClient()…);var result=await client.GetStringAsync(…);…;return finalResult;}
您不必更改任何已返回任务的第三方方法中的任何内容。没有“委托任务”这样的事情。您不需要更多任务,只需将Get
的签名更改为async Task Get
以使用wait即可*此*代码不是异步的,它实际上比原始代码*慢*因为它至少使用了两个额外的线程-为什么要使用await taks`insideStartNew
?只需返回您一直拥有的任务,如果这是家庭作业,您将以wait DoSomething()以外的任何方式失败代码>。如果你没有,要求退款,然后再找一个tutor@PanagiotisKanavos这是我强迫自己做的练习。在生活中,有时你不能完全控制整个代码库,有些签名你必须接受和处理。显然有人受不了这个想法,对我的答案投了否决票。顺便说一句:我对斯蒂芬·克利里的文章没有退款要求,那么你误解了这篇文章。我指的是你需要的想法
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();
}