繁体   English   中英

为什么 ASP.NET 长时间运行的页面请求会永远挂起客户端?

[英]Why ASP.NET long running page request hangs forever for the client?

我们遇到了一个用 C# 编写的 ASP.NET Web 应用程序的问题,该应用程序是用 Web 窗体(.ASPX 页面)构建的。 该系统中有一些长时间运行的请求,大约需要 10-15 分钟。 这是正常的,因为在后台运行繁重的 SQL 查询和计算。 这是一个旧系统,多年来从未出现过问题。 前段时间,一些用户开始抱怨他们无法下载运行时间超过 10 分钟的报告。 从技术上讲,他们通过点击页面上的按钮来运行报告,该按钮执行回发到生成报告的服务器,当文件准备好时,服务器将其返回给出现“保存文件”对话框的客户端(取决于浏览器)。 不幸的是,这些用户没有得到“保存文件”对话框,并且文件永远不会出现在浏览器中,并且页面似乎永远在加载。

正如我所说,我们以前从未见过这个问题(系统多年来一直托管在不同的服务器上)。 我们无法在本地开发环境中重现该问题。 最有趣的是,当我们尝试从位于客户公司 AD 域网络之外的个人计算机上运行相同的报告时,我们无法重现该问题。 但是,我们可以在 Postman 中运行示例请求时重现该问题。

以下是有关何时可以重现问题以及何时一切正常的简短摘要:

  1. 服务器 = [生产服务器,IIS 10,Windows Server 2019]; Client = [任何浏览器(Chrome 或 Edge),当这是客户端公司 AD 域网络中的工作站时]; 出现问题 => 是
  2. 服务器 = [生产服务器,IIS 10,Windows Server 2019]; Client = [任何浏览器(Chrome 或 Edge),当这是公司 AD 域网络之外的计算机时]; 出现问题 => 否
  3. 服务器 = [生产服务器,IIS 10,Windows Server 2019]; Client = [当客户端计算机位于公司 AD 域网络之外时发出 HTTP 请求或 PowerShell 脚本的邮递员或示例 C# 控制台应用程序]; 出现问题 => 是
  4. Server = [开发机,IIS 10,Window 10]; 客户端 = [来自公司 AD 域网络之外的计算机的任何浏览器(Chrome 或 Edge)]; 出现问题 => 否

我们做了各种实验,似乎这个问题出现在不同的用例中,如下所示:

  • 回发到 .ASPX 页面并在屏幕上重新加载一些内容
  • 回发并返回文件附件(浏览器中的“保存文件”对话框)
  • 仅返回一些纯文本作为响应的示例页面

我们发现,在所有这些情况下,如果 HTTP 请求花费的时间超过 240-250 秒,就会出现问题。

问题是没有出现错误,我们在浏览器的 DevTools (F12)、Postman 和 Fiddler 中都看不到任何指示。 它看起来好像请求挂起并且永远保持“待处理”。

我们在服务器上知道的所有“超时”设置都足够长。 例如,ASP.NET 会话超时为 60 分钟,httpRuntime 配置为 executionTimeout=3600,Application Pool Idle Time-out 设置为 0。

看起来如果 HTTP 请求花费的时间超过 240-250 秒,那么连接就会挂起,并且只发生在某些客户端上。 澄清一下,服务器上的进程每次都会完成,它执行所有任务都没有任何问题。

前段时间我们做了一个实验,如果服务器通过使用 Response.Write() 和 Response.Flush() 定期向客户端发送一些东西,而长 HTTP 请求仍在运行但我们不能使用,似乎问题不会出现当服务器将文件附件发送回浏览器时,此解决方法。 此外,除了找到解决方法外,我们还试图了解导致此问题的原因,并且我们正在寻找某种可用于控制此行为的服务器设置。

有任何想法吗?

可以肯定的是,超时设置是基于浏览器的,而不是基于服务器的。 因此,对浏览器的更新和升级将是发生这种情况的主要原因。 这些浏览器会收到许多更新——通常是每周一次。

事实上,您的测试表明并建议您根据所使用的客户端软件类型获得不同的结果 - 而不是对服务器的任何更改。

因此,使用 post-man 等会产生不同的结果。 这不是服务器端的事情 - 而是网络浏览器设置。 我建议您考虑生成数据或任何需要很长时间的新线程,完成后,它会设置一些会话值。

在浏览器端,你有一个客户端定时器(甚至是一个 asp.net 定时器)——比如每 5 或 10 秒调用一个 web 方法,它会返回数据生成的状态。 当数据生成和长部分完成后,客户端可以单击一个按钮,或者任何实际导航或显示结果的东西。

因此,您实际上是在与浏览器制造商以及他们对浏览器的更新作斗争——这是您无法控制的。 您可能会调整这个,然后调整那个 - 仅在几个月内看到基于 FireFox 的一些更新或他们正在使用的任何浏览器的大量新问题。

所以,我会为长时间运行的例程启动一个新的进程线程,然后回发发生。 如前所述,此时,可能会出现一个每 10 或 20 秒检查一次的简单计时器。 如前所述,它会/可以调用网络方法。 或者甚至如前所述,您可以使用 asp.net 计时器,它会每 10 秒触发一次,运行后面的代码以获取会话,如果数据准备好,则后面的代码可以导航到新页面,或填写网格或任何您用来呈现该数据的对象和手段。

从您发布的信息来看,您确实看起来像是在与浏览器制造商作斗争,以及各种客户端超时设置 - 通常不受您的控制。

任何等待的时间超过 10-15 秒? 然后你忽略了浏览器如何工作的基本概念。 (回发,稍等,然后得到回复)。 任何其他设计,然后这种类型的方法都忽略了基于 Web 的软件的整个架构。 回发导致的长时间等待很简单,不是一种可行的设计方法。 您可能已经摆脱了这种方法,但您现在显然为这种糟糕的假设和设计付出了代价。

任何需要超过 10 秒的响应时间都太长了。

例如,我有一些 PDF 文件。 我需要生成图像(预览)缩略图。

所以,我加载了网格,(显示一个通用图像)。 然后启动一个asp.net 计时器。

然后我检查这个过程是否完成(我使用了会话)。 完成后,我简单地刷新网格,并停止计时器。 因此,这种方法需要对现有代码进行最少的更改,但也提供了轮询和检查长时间运行进程状态的能力,并且无需任何花哨的 ajax,甚至不必编写客户端代码。

所以我有这个代码:

    If bolUpdate Then
        ' process the images as seperate process
        Dim MyP(1) As String        ' threads only allow one param
        MyP(0) = cPinfo.ProjectHeaderID
        MyP(1) = cPinfo.PortalComp
        Dim mypthread As New Thread(New ParameterizedThreadStart(AddressOf ProcessThumbs))
        mypthread.Start(MyP)
    End If

    If bolUpdate Then
        ' start a timer to update page when image processing done
        Timer1.Enabled = True
    End If

所以,我启动了一个单独的线程,然后启动定时器。

因此,计时器代码等待图像处理完成。 我每秒触发一次。

Protected Sub Timer1_Tick(sender As Object, e As EventArgs) Handles Timer1.Tick

    Dim strSQL As String = "SELECT * FROM myTable WHERE ProjectHeaderID = " &
                            cPinfo.ProjectHeaderID & " AND WebPreviewThumb is null"
    Dim rst As DataTable
    rst = Myrst(strSQL, GetConstr(cPinfo.PortalComp))
    If rst.Rows.Count = 0 Then
        ' no files to process left
        Timer1.Enabled = False
    End If

    LoadProofGrid()

End Sub

因此,在上面,它为网格显示拉取数据源,如果存在任何空图像预览 - 它会继续运行。 如果所有图像预览都完成,那么我会停止计时器。

而且我真的不必为每个循环重新加载网格视图,但我喜欢这种效果(如果你有 5 行,那么你会看到网格中的每个预览图像都会发生更新)。

现在,当我们完成时,我有一些特定的数据行要测试,但我也可以让进程线程设置一些 session() 值,如 session("ReportReady") = true,并在计时器事件中检查它。

所以,我不会将你的软件设计建立在希望、翅膀和祈祷的基础上,但事实上,添加一点代码来循环等待这种情况发生。 好的部分是一个计时器事件,然后你可以有某种类型的进度条,甚至是一个不错的请等待 - 为用户处理。

并且使用定时器事件很简单,而且很好,因为这里不需要新的标记(定时器控件除外),甚至不需要引入客户端 JavaScript。 然而,在大多数情况下,最好设置这样的计时器代码客户端,使用 ajax 调用,并获得一些关于何时完成较长运行过程的状态。

但是,上面的计时器方法往往意味着对现有页面标记的更改要少得多。 并且由于 asp.net 计时器确实会导致回发,因此将其放置在一个简单的小型更新面板中,并且在此等待期间您不会遭受整页回发和重新绘制。 完成该过程后,您的代码现在可以继续呈现报告或执行任何操作。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM