HttpResponse.End与HttpResponse.Close与HttpResponse.SuppressContent的区别

40

在ASPX页面中,我想要在特定的代码路径下结束响应(而不是由于错误条件),以便在流中不发送任何其他内容。 所以我自然会使用以下代码:

Response.End();

这会导致ThreadAbortException异常,这是有意为之的设计

以下代码似乎有效,但不符合此SO问题中建议的正确做法:

Response.Flush();
Response.Close();

这样怎么样?

Response.Flush();
Response.SuppressContent = true
然后让页面正常完成。我可以处理和忽略ThreadAbortException,但我想知道使用SuppressContent方法是否有什么问题或陷阱?举个例子,假设我有一个ASPX页面,在其中可能会将内容类型更改为多种可能性之一。根据内容类型和场景,在代码的给定点上,我想要防止向客户端发送更多内容。在将SuppressContent设置为true之后,假设没有任何进一步的服务器端代码问题,我只是不希望向客户端发送任何其他内容。请参见下面提供的答案以获取我实际最终采用的解决方案,因为我最终找到了一种避免使用Response.End时出现ThreadAbortException的方法。

听起来你需要仔细检查你的代码,看看在你完成后还有什么其他东西可能会写入响应流。这一切听起来有点像“意大利面条式编程”。 - Jeremy McGee
代码后台没有其他的Response.Writes或任何其他输出。问题在于,如果页面实际上写出一个XML文档而不是正常的HTML页面输出,一旦我将内容类型更改为XML并将XML写出,我不希望呈现ASPX页面的其余部分 - 这显然会破坏XML。 - AdaTheDev
请注意,您始终可以更改已接受的答案,即使现在是11年后... - Heretic Monkey
6个回答

48
我知道这是一个老问题,但我把它包含在内,以便有人不小心看到这篇文章。我一直在追踪一个错误,导致我重新审查了我的Response.End的使用,并发现了一个比这个问题晚一年的MSDN文章,可以总结为“永远不要使用Response.End”。以下是设计IIS7集成管道的Thomas Marquardt关于此的说法:
End方法也在我的“永远不使用”列表中。停止请求的最佳方法是调用HttpApplication.CompleteRequest。End方法只是因为我们在1.0发布时尝试与Classic ASP兼容而存在。Classic ASP有一个终止ASP脚本处理的Response.End方法。为了模仿此行为,ASP.NET的End方法试图引发ThreadAbortException。如果成功,则调用线程将被中止(非常昂贵,不利于性能),并且管道将跳转到EndRequest事件。当然,ThreadAbortException如果成功意味着线程在调用任何更多代码之前展开,因此调用End意味着你将不会调用之后的任何代码。如果End方法无法引发ThreadAbortException,则它将刷新响应字节到客户端,但它会同步执行,这对性能来说真的很糟糕,并且当End后的用户代码执行完成后,管道将跳转到EndRequest通知。将字节写入客户端是一个非常昂贵的操作,特别是如果客户端在世界的另一端并使用56k调制解调器,因此最好异步发送字节,这就是请求正常结束时我们所做的。同步刷新真的很糟糕。因此,总结一下,您不应该使用End,但使用CompleteRequest是完全可以的。End的文档应说明CompleteRequest是跳过到EndRequest通知并完成请求的更好方法。

来源: http://blogs.msdn.com/b/aspnetue/archive/2010/05/25/response-end-response-close-and-how-customer-feedback-helps-us-improve-msdn-documentation.aspx

本文介绍了ASP.NET中的Response.End()和Response.Close()方法的差别,以及如何根据客户反馈优化MSDN文档。


6
好的发现。这绝对应该是被接受的答案。 - Igor Brejc
+1 有趣的发现。下次遇到这种情况,我一定会使用CompleteRequest。但是需要注意,它与调用"CompleteRequest"方法后执行的代码不同。只是需要留意的小事情。 - eglasius
+1,这也解决了我的问题。我有两个问题导致了它,都需要解决,所以如果有人应用它并且没有帮助,请考虑这个:https://dev59.com/k3DXa4cB1Zd3GeqP7wkB#YNIWoYgBc1ULPQZFp5uy - Cee McSharpface
3
建议结合Response.SuppressContent使用,以免人们在某些模块中意外地更新您的响应,如@DanielCuadra在此处建议的那样:https://dev59.com/12Uq5IYBdhLWcg3wXvIC#36968241。 - user420667

12

最终我发现了一个简单的解决方案,可以在不出现ThreadAbortException的情况下使用Response.End()。

Response.Flush();
Response.End();

从我的原始问题来看,我一直在尝试只在将一些内容发送到响应流之后使用 Response.End()。

似乎如果在执行 Response.End() 时存在未刷新的内容,就会出现 ThreadAbortException 异常。通过在 End 之前立即进行 Flush,ThreadAbortException 实际上不会被抛出。

看起来很好用——现在在使用 Response.End 时没有抛出 ThreadAbortException。


8
我已经点赞了这个答案,但实际测试后发现它并不起作用。要进行测试:在您的代码后端将Response.End包装在try catch块中,在catch块内进行response.write(ex.Message)。您将看到“正在终止线程。”消息。尽管如此,似乎没有其他方法来结束响应并停止代码执行,因此只需在代码后面包装try catch,并在catch中执行Server.ClearError(); * P.S. 我不确定这样做的实际意义是什么...* - Registered User
2
异常被抛出,而且End()的第一件事就是刷新,所以我不明白这会有什么区别。而且确实没有。*"将当前所有缓冲输出发送到客户端" http://msdn.microsoft.com/en-us/library/system.web.httpresponse.end%28VS.80%29.aspx - mhenry1384
我也刚测试了一下。我认为,除非上下文仍处于可取消期间(不知道是什么意思),否则Response.End总是会抛出异常。http://referencesource.microsoft.com/#System.Web/HttpResponse.cs,4a66c734706db954 - user420667
1
也进行了测试,如果在Flush()之后再调用End(),仍然会在catch中抛出ThreadAbortException异常。 - Tikhon

4

更新 - 警告: 有更好的方法,请不要使用此方法,而是查看Ethan的答案!

我不认为有充分的理由避免使用Response.End。想要通过让页面请求周期继续并进行不必要的额外工作来避免ThreadAbortException的成本似乎不太合适。

ThreadAbortException是一种特殊类型的异常,旨在停止线程执行(即使捕获到它,它也会自动重新抛出)。也就是说,在某些情况下,它可能会造成伤害(请参见ThreadAbortException末尾添加的社区内容)。

除非您处于其中一种情况,否则应坚持使用Response.End。请注意,周围有一些用法,会进行SuppressContent和Response.End,我猜这是为了避免从内部Response.Flush获取的某些东西。


我认为这总结了我从研究中得到的整体图片。看到有几篇文章使用SuppressContent/Response.End方法。指向链接中的社区内容部分也很有用。 - AdaTheDev
4
请确保阅读 @Ethan 的答案,永远不要使用 Response.End() - Igor Brejc
阅读后,我完全同意。在我的答案顶部添加一个注释。 - eglasius

3
这是一个非常常见的问题。除了Response.End(),几乎总是错误的调用其他函数。以下是来自MSDN的End()的描述:

发送当前缓冲区中的所有输出到客户端,停止页面的执行,并引发EndRequest事件。

这似乎正是您想要做的事情。您会注意到在最后一部分中它会引发EndRequest事件。这意味着在调用End()之后,所有在End()之前编写的数据都被刷新到客户端,套接字被关闭,资源被释放,并且您的程序立即停止处理请求。

3
除了你总是会遇到ThreadAbortException的小问题外,Response.End()对我来说确实有效。这不是一个会触发Response.End()的异常代码路径,所以处理这个异常成为常态感觉不对。这让我开始思考“必须有另一种方法”- 因此我想知道是否使用SuppressContent方法存在任何真正的问题。 - AdaTheDev

1
如果在调用Response.SuppressContent = true之后,您的页面继续对数据库中的记录进行更改,会发生什么?


是的,进一步的服务器端代码将被执行,因此可以执行更多的工作,比如进行数据库操作。在我的情况下,这不是问题;我只是不希望从那时起有任何更多的内容被写入响应中。 - AdaTheDev

1
就我所了解的代码,使用Flush/SuppressContent方式反映HttpResponse会使您容易受到在flush后尝试进行头更改的代码的攻击。在flush之后更改标题会导致HttpException。
我会使用Response.End()来确保没有其他东西会干扰响应。
如果您想继续执行但同时短路http管道,请考虑使用Response.Flush()和HttpContext.Current.ApplicationInstance.CompleteRequest(),就像Response.End()在上下文不可取消期间时所做的那样。

在我完成flush/suppresscontent之后,将不会有任何标题更改。实际上,可能不会有其他任何事情发生。除非我正在编写一个XML文档,并相应地设置内容类型,否则我真的只想防止任何进一步的ASPX页面呈现内容(这将破坏我已经呈现出来的XML)。 - AdaTheDev
CompleteRequest方法似乎不起作用。在调用该方法后,仍会向客户端写入更多内容(例如我不想要的母版页内容)。 - AdaTheDev

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接