正如 @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)自动生成的,并且由于这个非常具体的问题,我们不打算进行更改。
我在这个周末读了很多关于它的内容,据我推测,这可能是 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);
}
}
}
我认为对
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();
中放置了断点来了解阻塞主线程的死锁。