我正在尝试向现有的asp.net窗体应用程序添加异步功能。但是我遇到了页面传输/重定向的问题。原因是许多现有的代码重定向/传输都是从实用程序类或自定义控件等进行的。此外,在重定向/传输之后存在的代码需要避免在堆栈上进一步向上。原始代码利用了内部调用Response.End的重定向/传输,并通过ThreadAbortException缩短了线程的代码执行。
对于Server.Transfer和Response.Redirect,通过添加Async="true“使其异步的页面会出现ThreadAbortException气泡,并且浏览器会以错误响应结束。我假设由于页面是异步的,所以当前页面请求在传输页面之前完成,而不是线程中止的同步方式,然后将开始处理传输页面。
当我使用Response.Redirect( "somepagename",false)或Server.Execute时,它们会阻止当前响应结束,所以我在它们后面添加了ApplicationInstance.CompleteRequest,这会跳过以后的事件和处理程序管道处理。但是重定向/执行之后的代码会继续,这会导致其他错误/问题。(例如,根据ThreadAbortException最初会跳过的逻辑,再次调用Response.Redirect转到不同的页面。)
我还尝试了在Server.Execute和ApplicationInstance.CompleteRequest之间切换Response.FlushAsync,在浏览器上看起来没问题。因为FlushAsync从传输页面发送缓冲的响应,所以CompleteRequest之后的异常不会显示给浏览器(至少在开发中)。但是,如果第一页的刷新或继续代码执行首先完成,则仍可能存在竞争条件。并且CompleteRequest之后的后续代码仍在执行,这可能会产生副作用。
原始代码:
public static void GoToPage( string page, bool transfer )
{
if ( transfer ){HttpContext.Current.Server.Transfer( page, false ); }
else { HttpContext.Current.Response.Redirect( page ); }
}
发布于 2019-03-09 05:20:12
我有一个解决方案。我将调用的Server.Transfer部分更改为:
if ( ( HttpContext.Current.Handler as Page )?.IsAsync ?? false )
{
if ( transfer )
{
HttpContext.Current.Server.Execute( page, false );
HttpContext.Current.Response.Flush();
Thread.CurrentThread.Abort();
}
else
{
HttpContext.Current.Response.Redirect( page, false );
Thread.CurrentThread.Abort();
}
}
else
{
if ( transfer )
{
HttpContext.Current.Server.Transfer( page, false );
}
else
{
HttpContext.Current.Response.Redirect( page );
}
}
然后更新了异步click even处理程序以捕获ThreadAbortException。异常处理程序调用ResetAbort和CompleteRequest。
private async void SubmitButton_Click( object sender, EventArgs e )
{
try
{
//Awaited async call that returns a value.
//Call to utility classes that eventually call GoToPage( string page, bool transfer )
}
catch ( ThreadAbortException tEx )
{
Thread.ResetAbort();
HttpContext.Current.Response.SuppressContent = true;
HttpContext.Current.ApplicationInstance.CompleteRequest();
}
}
解释:在ASP.NET中,在同步请求处理程序中,Server.Transfer和Response.Redirect (通过内部调用Response.End的方式)将调用Thread.Abort (触发ThreadAbortException),通过将请求缓冲区同步刷新到客户端浏览器来执行阻塞调用。然后调用ApplicationInstance.CompleteRequest(),跳过更多的事件和流水线模块。
因此,在异步处理程序的情况下,将在内部调用CompleteRequest()而不是Response.End,不会抛出ThreadAbortException,并且在调用Transfer或Redirect之后代码将继续执行。我的页面的单击事件处理程序是一个异步的void处理程序,所以编译器将它设为异步的,这样我们就会得到下游的异步Server.Transfer Response.Redirect问题。
解决方案: Server.Execute也运行我想要传输的页面的代码,并填充响应缓冲区。刷新将缓冲区刷新到浏览器。我更喜欢FlushAsync,避免同步刷新,但包含方法不是异步的。Abort抛出ThreadAbortException并绕过它后面的代码。Response.Redirect与此类似。将endReponse参数设置为false,这样它就不会在内部调用Response.End。然后中止线程以跳过堆栈中该线程后面的后续代码。
在单击事件处理程序中,我压缩了异常处理程序中的ThreadAbortException。ResetAbort防止在处理程序之后自动重新抛出异常。如果没有ResetAbort,代码将在后续事件中执行。SuppressContent可防止当前页面和传输页面的内容同时显示给用户。这在某些情况下会发生,具体取决于异步代码所在的位置。CompleteRequest跳过事件和http模块并跳转到EndRequest事件。
我确实尝试在异步部分使用PageAsyncTask和相关功能,以避免异步空点击处理程序,但它只会启动任务。它实际上并没有被等待,所以它最终会在我需要它在完成之前返回的值的地方执行代码。将整个事件处理程序代码放在PageAsyncTask中也不起作用,因为我需要它在事件处理程序中执行,而不是稍后执行。ExecuteRegisteredAsyncTasks仅适用于旧版asp.net同步上下文。不适用于AspNetSynchronizationContext。至少在.NET 4.6.1中没有。转换为.NET核心不是一个选项。
这不是我所希望的解决方案。希望有比我更有见识的人有更好的。这并不需要我录制一半的现有节目。
https://stackoverflow.com/questions/55055468
复制相似问题