【问题标题】:How to abort old request processing when new request arrives on ASP.NET MVC 5?当新请求到达 ASP.NET MVC 5 时,如何中止旧请求处理?
【发布时间】:2017-06-07 14:31:12
【问题描述】:

我有一个包含数百个复选框和下拉菜单的表单(其中许多值是耦合在一起的)。在动作中有更新机制来更新会话中的对象。该对象执行所有值的验证和耦合,例如,如果用户在一个输入字段中键入 %50,我们可能会在下拉列表中添加 3 个新的 SelectListItem。

一切正常,但如果用户开始很快点击复选框(这是我们场景中的正常情况),控制器在处理之前的帖子时会收到多个帖子。幸运的是,我们只对最后一个 POST 感兴趣,因此我们需要一种方法来在来自同一表单的新请求到来时中止\取消正在进行的请求。

我尝试了什么: 1- 当服务器仍在处理前一个帖子时,阻止客户端发布多个帖子。这是不可取的,因为它会在浏览器端产生明显的暂停。

2- 有几种解决方案可以通过使用 HASH 码或 AntiForgeryToken 来阻止多个回发。但它们不是我需要的,我需要中止正在进行的线程以支持新请求,而不是阻塞传入的请求。

3- 我尝试通过添加两个消息处理程序(一个在操作之前,另一个在执行操作之后)来扩展管道以保留哈希码(或 AntiForgeryToken),但问题仍然存在,即使我可以检测到有正在进行的线程处理相同的请求,我无法中止该线程或将旧请求设置为 Complete。

有什么想法吗?

【问题讨论】:

  • 您应该寻求实施客户端解决方案,以消除不断回发到控制器的需要。然后,您的 SPA 可以在用户保存或其他触发时回发整个对象状态。
  • 不能将超过 2400 行的代码从 C# 移动到 Javascript。我们拥有 C# 中的所有逻辑,包括验证和数据耦合。我们的解决方案也不是 SPA,即使在这种情况下看起来像 SPA 行为。
  • 我很好的方法是构建一个弹跳系统,想象你有一个命令数组,所以每次更改复选框时,你都会存储这个操作,并且你每秒都会运行一个超时函数,这个函数将寻找数组取最后一个元素,执行请求并清除它。这样,最后一次操作每分钟只有一个请求

标签: asp.net asp.net-mvc asp.net-mvc-5


【解决方案1】:

可以做的唯一一件事就是在客户端限制请求。基本上,您需要在单击复选框时设置超时。您可以让该初始请求通过,但随后任何进一步的请求都会排队(或实际上在您的场景中的第一个排队请求之后被丢弃)并且在超时清除之前不要运行它。

无法在服务器端中止请求。每个请求都是幂等的。之前或之后发生的任何事情都没有内在的知识。服务器有多个线程处理请求,并且会尽可能快地处理这些请求。如何处理请求或如何发送响应没有顺序。第一个请求可能是第三个收到响应的请求,这取决于每个请求的处理方式。

【讨论】:

    【解决方案2】:

    您正在尝试通过异步技术实现事务功能(即仅计算最后一个请求)。这是一个设计缺陷。

    由于您拒绝在客户端阻止,因此您无法控制首先处理哪些请求,或者在客户端再次正确处理结果。

    您实际上可能会遇到这种情况:

    1. 客户端发送请求 A
    2. 服务器开始处理请求 B
    3. 客户端发送请求 B
    4. 服务器开始处理请求 B
    5. 服务器返回请求B的结果,客户端相应改变
    6. 服务器返回请求 A 的结果,客户端相应地进行更改(并撤消先前由请求 B 产生的更改)

    阻止是确保正确顺序的唯一方法。

    【讨论】:

      【解决方案3】:

      感谢您的帮助@xavier-j。 在玩了这个之后,我写了这个。希望它对需要相同东西的人有用:

      首先你需要添加这个 ActionFilter

      public class KeepLastRequestAttribute : ActionFilterAttribute
      {
          public string HashCode { get; set; }
          public override void OnActionExecuting(ActionExecutingContext filterContext)
          {
              base.OnActionExecuting(filterContext);
              Dictionary<string, CancellationTokenSource> clt;
              if (filterContext.HttpContext.Application["CancellationTokensDictionary"] != null)
              {
                  clt = (Dictionary<string, CancellationTokenSource>)filterContext.HttpContext.Application["CancellationTokensDictionary"];
              }
              else
              {
                  clt = new Dictionary<string, CancellationTokenSource>();
              }
              if (filterContext.HttpContext.Request.Form["__RequestVerificationToken"] != null)
              {
                  HashCode = filterContext.HttpContext.Request.Form["__RequestVerificationToken"];
              }
      
              CancellationTokenSource oldCt = null;
              clt.TryGetValue(HashCode, out oldCt);
              CancellationTokenSource ct = new CancellationTokenSource();
              if (oldCt != null)
              {
                  oldCt.Cancel();
                  clt[HashCode] = ct;
              }
              else
              {
                  clt.Add(HashCode, ct);
              }
      
              filterContext.HttpContext.Application["CancellationTokensDictionary"] = clt;
              filterContext.Controller.ViewBag.CancellationToken = ct;
          }
      
          public override void OnResultExecuted(ResultExecutedContext filterContext)
          {
              base.OnResultExecuted(filterContext);
              if (filterContext.Controller.ViewBag.ThreadHasBeenCanceld == null && filterContext.HttpContext.Application["CancellationTokensDictionary"] != null) {
                  lock (filterContext.HttpContext.Application["CancellationTokensDictionary"])
                  {
                      Dictionary<string, CancellationTokenSource>  clt = (Dictionary<string, CancellationTokenSource>)filterContext.HttpContext.Application["CancellationTokensDictionary"];
                      clt.Remove(HashCode);
                      filterContext.HttpContext.Application["CancellationTokensDictionary"] = clt;
                  }
              }
          }
      }
      

      我在这里使用 AntiForgeryToken 作为密钥令牌,您可以添加自己的自定义哈希码以获得更多控制权。

      在控制器中你会有这样的东西

              [HttpPost]
          [KeepLastRequest]
          public async Task<ActionResult> DoSlowJob(CancellationToken ct)
          {
              CancellationTokenSource ctv = ViewBag.CancellationToken;
              CancellationTokenSource nct = CancellationTokenSource.CreateLinkedTokenSource(ct, ctv.Token, Response.ClientDisconnectedToken);
      
              var mt = Task.Run(() =>
              {
                  SlowJob(nct.Token);
              }, nct.Token);
              await mt;
      
              return null;
          }
      
          private void SlowJob(CancellationToken ct)
          {
              for (int i = 0; i < 10; i++)
              {
                  Thread.Sleep(200);
                  if (ct.IsCancellationRequested)
                  {
                      this.ViewBag.ThreadHasBeenCanceld = true;
                      System.Diagnostics.Debug.WriteLine("cancelled!!!");
                      break;
                  }
                  System.Diagnostics.Debug.WriteLine("doing job " + (i + 1));
              }
              System.Diagnostics.Debug.WriteLine("job done");
      
              return;
          }
      

      最后,在您的 JavaScript 中,您需要中止正在进行的请求,否则浏览器会阻止新请求。

              var onSomethingChanged = function () {
      
              if (currentRequest != null) {
                  currentRequest.abort();
              }
      
              var fullData = $('#my-heavy-form :input').serializeArray();
      
              currentRequest = $.post('/MyController/DoSlowJob', fullData).done(function (data) {
                  // Do whatever you want with returned data
              }).fail(function (f) {
                  console.log(f);
              });
              currentRequest.always(function () {
                  currentRequest = null;
              })
          }
      

      【讨论】:

        猜你喜欢
        • 2021-02-06
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2011-04-20
        • 1970-01-01
        相关资源
        最近更新 更多