C# 使用异步代码时的特定死锁场景

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

关于基于任务的异步模式,我已经读了好几个小时了。 现在我试着证明自己我完全理解了它,所以我强迫自己做了以下练习:

在WebApi控制器内部,等待非
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中调用而不使其异步(这是本练习要求的一部分)吗?你是说像HttpClient
async Task Get(){var client=new HttpClient()…);var result=await client.GetStringAsync(…);…;return finalResult;}
您不必更改任何已返回任务的第三方方法中的任何内容。没有“委托任务”这样的事情。您不需要更多任务,只需将
Get
的签名更改为
async Task Get
以使用
wait即可*此*代码不是异步的,它实际上比原始代码*慢*因为它至少使用了两个额外的线程-为什么要使用
await taks`inside
StartNew
?只需返回您一直拥有的任务,如果这是家庭作业,您将以
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();
    }