【问题标题】:asp.net while first request processing allow second request on clickasp.net 而第一个请求处理允许点击第二个请求
【发布时间】:2018-07-13 11:33:52
【问题描述】:

在 Asp.net Web Form 应用程序中,如果需要很长时间,我有生成报告按钮和取消按钮来取消报告生成过程。

当我单击“生成报告”时,它会执行繁重的任务,3 秒后我尝试通过单击“取消”按钮取消此繁重的任务。

但是用于取消按钮单击的服务器端代码会在一段时间后被调用。

我什至尝试在JavaScript 中使用window.stop() 来停止页面加载并快速访问服务器代码,但仍然存在延迟。

代码:

protected void btnExportExcel_Click(object sender, EventArgs e)
{

// Doing Heavy Task to Generate Report

}


protected void btnCancel_Click(object sender, EventArgs e)
{
    if (Response.IsClientConnected)
    {

       HttpContext.Current.ApplicationInstance.CompleteRequest();

    }
}     

  <asp:Button ID="btnCancel" runat="server" Text="Cancel Request" 

  OnClientClick="return StopPageLoading();" OnClick="btnCancel_Click" />


 function StopPageLoading() {

     try
     {
       window.stop();
     } catch (exception) 
     {
       document.execCommand('Stop'); // for IE and other browsers
     }

  }

当当前请求正在处理中时,如何允许在单击时快速启动另一个请求?

如何让UI做出响应?

更新:

重现情况:

  1. 我点击 Export To Excel ,需要 7 分钟处理。
  2. 当我仍在处理时,我单击了取消按钮,需要另外 5 分钟才能取消。

我读到 Concurrent request 在 Asp.NET 中是不可能的,因为会话状态会产生排他锁。

那么如何快速取消呢?

让我的方法async 有助于克服会话状态排他锁问题吗?

【问题讨论】:

  • 您能告诉我们您使用的 .Net-Framework 版本吗?
  • 当您单击取消按钮时,您会延迟多少时间?
  • 您必须使用 ajax 调用 webservice/api 来使 UI 响应。普通的 PostBack 无法做到这一点。
  • @VDWWD ,好吧,可以说 ExportExcel 点击是正常的 PostBack 调用,而取消按钮点击是 ajax 调用。当ExportExcel click 正在处理时,取消按钮点击事件会很快发生吗?
  • @Darem ,我正在使用 .NET 4.5 并且延迟为 5 分钟,单击导出到 excel 后我等待 5 分钟,当它仍在处理时,我单击取消按钮,现在它应该停止处理速度快。但取消需要时间。

标签: c# asp.net .net webforms


【解决方案1】:

就像你的another question一样,你可以这样做

public partial class Contact : Page
{

    private static CancellationTokenSource tokenSource = new CancellationTokenSource();


    protected void Page_Load(object sender, EventArgs e)
    {

    }

    protected async void btnExportExcel_Click(object sender, EventArgs e)
    {
        CancellationToken cToken = tokenSource.Token;
        cToken.Register(() => cancelNotification());

        try
        {

            await Task.Run(() =>
            {
                cToken.ThrowIfCancellationRequested();

                GenerateReport(sender, cToken);

            }, cToken);
        }
        catch(OperationCanceledException)
        {

        }
    }

    private void GenerateReport(object sender, CancellationToken ct)
    {
        // Just to Simulate Export to excel

        Thread.Sleep(70000);
    }


    protected void btnCancel_Click(object sender, EventArgs e)
    {

        tokenSource.Cancel();

    }


    private static void cancelNotification()
    {
        // Do your stuff when the user is canceld the report generation

    }
}

在你的Page.aspx

需要这样的东西

<%@ Page Async="true" EnableSessionState="false" %>

希望对你有帮助!

【讨论】:

    【解决方案2】:

    虽然,你可以做或不做(我不确定),在 ASP.NET 中创建一个长时间运行的任务通常不是一个好主意,特别是如果它们是后台任务。 所以有一些帮助库就是为此目的。

    1. 挂火http://docs.hangfire.io Hangfire 允许您以一种非常简单但可靠的方式在请求处理管道之外启动方法调用。这些方法调用在后台线程中执行,称为后台作业。 此支持使用取消令牌取消后台作业
    2. Quarz.NET 我从未尝试过 Quarz.NET
    3. 或者你必须自己做。我知道一些项目需要一些专用服务器来运行后台和耗时的任务。所以网站只是一个前端层,设置了一些控制任务是否取消的标志

    【讨论】:

      【解决方案3】:

      问题出在 IIS(或任何其他 Web 服务器)架构中。服务器向客户端(浏览器)返回响应并忘记它。您的第一个请求 (btnExcelExport_Click) 必须在向浏览器返回响应之前完成。单击btnCancel 会启动一个新请求,该请求对第一个请求一无所知。
      解决方法是将长作业发送到另一个thread。可以使用发送到新线程的状态object 与该线程通信。像这样。

      <%@ Page Language="C#" %>
      
      <!DOCTYPE html>
      
      <script runat="server">
      
          protected void btnRun_Click(object sender, EventArgs e)
          {
              var jobState = new StateInfo()
              {
                  Id = 1,
                  Counter = 0,
                  Content = "Start the job",
                  Cancelled = false,
                  Completed = false
              };
              Session["job"] = jobState; //Save state between requests
              System.Threading.ThreadPool.QueueUserWorkItem(
                  new System.Threading.WaitCallback(LongJob),
                  jobState
                  );//returns immediately
              lblState.Text += "<br />" + jobState.Counter.ToString() 
                  + " Completed: " + jobState.Completed.ToString()
                  + " Cancelled: " + jobState.Cancelled.ToString()
                  + "<br />" + jobState.Content;
              btnCancel.Visible = true;
              btnCheck.Visible = true;
          }
      
          protected void btnCancel_Click(object sender, EventArgs e)
          {
              var jobState = Session["job"] as StateInfo;
              if (!jobState.Completed)
                  jobState.Cancelled = true;
              System.Threading.Thread.Sleep(1000);//wait for the next loop to complete
              lblState.Text += "<br />" + jobState.Counter.ToString()
                  + " Completed: " + jobState.Completed.ToString()
                  + " Cancelled: " + jobState.Cancelled.ToString()
                  + (jobState.Completed || jobState.Cancelled ? "<br />" + jobState.Content : "");
          }
      
          protected void btnCheck_Click(object sender, EventArgs e)
          {
              var jobState = Session["job"] as StateInfo;
              lblState.Text += "<br />" + jobState.Counter.ToString()
                  + " Completed: " + jobState.Completed.ToString()
                  + " Cancelled: " + jobState.Cancelled.ToString()
                  + (jobState.Completed || jobState.Cancelled ? "<br />" + jobState.Content : "");
          }
      
          private void LongJob(object state)
          {
              var jobState = state as StateInfo;
              do
              {
                  System.Threading.Thread.Sleep(1000);
                  jobState.Counter++;
                  if (jobState.Counter >= 100)
                  {
                      jobState.Completed = true;
                      jobState.Content = "The job is completed";
                  }
                  else if (jobState.Cancelled)
                      jobState.Content = "The job is cancelled";
              }
              while (!jobState.Cancelled && !jobState.Completed);
          }
          [Serializable]
          class StateInfo
          {
              public int Id { get; set; }
              public int Counter { get; set; }
              public string Content { get; set; }
              public bool Cancelled { get; set; }
              public bool Completed { get; set; }
          }
      
      </script>
      
      <html xmlns="http://www.w3.org/1999/xhtml">
      <head runat="server">
          <title>Long Job</title>
      </head>
      <body>
          <form id="form1" runat="server">
              <div>
                  <asp:Label ID="lblState" runat="server"></asp:Label><br />
                  <asp:Button runat="server" ID="btnRun" OnClick="btnRun_Click" Text="Run" />
                  <asp:Button runat="server" ID="btnCheck" OnClick="btnCheck_Click" Text="Check State" Visible="false" />
                  <asp:Button runat="server" ID="btnCancel" OnClick="btnCancel_Click" Text="Cancel" Visible="false" />
              </div>
          </form>
      </body>
      </html>
      

      【讨论】:

        【解决方案4】:

        您是否尝试过在代码中使用 async/await 操作而不是同步操作

        https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/async/

        如果报告生成调用是异步的,则在报告生成过程开始后重定向到 UI。现在可以在需要时单击取消按钮。

        这有帮助吗?

        【讨论】:

        • 感谢您的写作,我们没有在项目中使用async/await,所以我不想在没有深入了解的情况下实现它,因为如果没有正确实现它可能会破坏现有功能。那么你有同步请求的解决方案吗?
        • 请检查您的报表服务(在 IIS 级别)或 SQL 连接是否可以设置超时并返回异常,并且异常可以返回到 UI。其他选项是使用线程并在特定时间后中止。
        • 这句话then redirect to UI once the report generation process is kicked off,我不太明白。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2022-01-21
        • 2014-03-31
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多