ASP.NET .Net Framework 4.8 中的 Async/Await 错误 - 线程卡住并且从不回调

问题描述 投票:0回答:1

正如 @Charlieface 和 Srinivasan Jayakumar 所指出的。创建一个新线程来调用代码解决了问题,我知道这不是最佳实践,但最佳实践和非常旧的遗留应用程序不能很好地沟通。我无法找到一种方法来调试 System.Web.AspNetSynchronizationContext 并找出为什么这个死锁在这里而不是在 Windows 服务中。不管怎样,已经解决了,谢谢大家

我在 ASP.NET .net Framework 4.8 中有一个非常奇怪的错误。如果我通过反射加载一个类,请调用一个同步方法,该方法内部使用

.Wait()
.GetAwaiter().GetResult()
调用异步方法。如果此方法使用任何没有
.ConfigureAwait(false)
的等待,则应用程序将挂起,等待将永远不会返回,并且 IIS 会挂起(一段时间后,它会恢复正常,可能是在应用程序池重新启动时)。 PS:在此示例中,IIS 不会挂起,它接受新请求,调用永远不会返回并且线程被卡住,但在原始应用程序中会发生这种情况。

这只发生在 ASP.NET .net Framework 4.8 中,代码由 .net Framework 4.8 编写的一个服务和 .net 8 编写的另一个服务共享,没有任何问题(DLL 有多个目标)。

我找到了一些解决方法:

1 - 如果我将页面更改为异步,并且从上到下将所有内容更改为异步,我不会遇到错误 --> 不是一个选项,因为它是一个遗留的整体。
2 - 将方法内的每个调用更改为

.ConfigureAwait(false)
-> 也不是一个选项,大部分代码是由我们开发的一些工具(主要是 ORM)自动生成的,并且由于这个非常具体的问题,我们不打算进行更改。
3 - 将方法转为同步并删除所有异步调用 -> 我们采用了这个方法,即使我们不喜欢该解决方案,它的影响也非常小,因为这是一个非常具体的代码。

我在这个周末读了很多关于它的内容,据我推测,这可能是 ASP.NET 使用的自定义 SynchronizationContext 的问题,并且回调被搞乱了,但由于它挂起,我不能真正确定它让我很困惑。

编辑澄清:我真正想知道的是如何调试这些情况,以及这种情况下到底发生了什么。由于状态机是由编译器创建的,我真的不知道如何调试并了解为什么在应该恢复时胎面会丢失

我上传了一个示例来重现错误(GitHUB 上的示例),只需更正 DLL 的路径并启动网站,您就会收到错误。

对组件的调用。

 protected void Page_Load(object sender, EventArgs e)
 {
     string Lstr_PathDLL = "full path to DLL";                   
     Assembly Lobj_Assembly = Assembly.LoadFile(Lstr_PathDLL);            
     IAwaitErrorNetFrameworkASPNET Lobj_Error = (IAwaitErrorNetFrameworkASPNET)Lobj_Assembly.CreateInstance("LoadedByReflection.AwaitErrorNetFrameworkASPNET");
     Lobj_Error.Test();
     
 }

组件中的类

using Dominio;
using System.Threading.Tasks;

namespace LoadedByReflection
{
    public class AwaitErrorNetFrameworkASPNET : IAwaitErrorNetFrameworkASPNET
    {


        public void Test()
        {
            Teste2().GetAwaiter().GetResult();
        }



        private async Task Teste2()
        {
            await Task.Delay(1000);
        }
    }
}
c# asp.net .net-4.8
1个回答
1
投票

我认为对

Teste2().GetAwaiter().GetResult();
的调用会在您的代码中产生问题。通过使用它,当前线程将等待所调用函数的返回值。它在调用 page_load 的同一线程上运行。由于 Teste2() 是 void 函数并且不返回任何返回值,因此邮件线程永远等待 Teste2() 的返回值。为了避免这种死锁,您需要使用 Task 类在单独的线程中运行 Teste2()。我修改了你的 Githib 代码并发现它有效。

请替换为以下代码,

namespace LoadedByReflection
{
    public class AwaitErrorNetFrameworkASPNET : IAwaitErrorNetFrameworkASPNET
    {


        public void Test()
        {
            //Teste2().GetAwaiter().GetResult();
            Task callTask = Task.Run(() => Teste2());
            callTask.Wait();
        }



        private async Task Teste2()
        {
            await Task.Delay(1000);
        }
    }
}

关于调试,我在

Teste2().GetAwaiter().GetResult();
中放置了断点来了解阻塞主线程的死锁。

© www.soinside.com 2019 - 2024. All rights reserved.