当新请求到达ASP.NET MVC 5时,如何中止旧请求处理?

当新请求到达ASP.NET MVC 5时,如何中止旧请求处理?,asp.net,asp.net-mvc,asp.net-mvc-5,Asp.net,Asp.net Mvc,Asp.net Mvc 5,我有一个包含数百个复选框和下拉菜单的表单(其中许多复选框和下拉菜单的值是耦合在一起的)。在操作中,有更新机制来更新会话中的对象。此对象执行所有值的验证和耦合,例如,如果用户在一个输入字段中键入%50,我们可能会在下拉列表中添加3个新的SelectListItem 一切正常,但如果use很快开始点击复选框(这在我们的场景中是正常情况),controller在处理之前的帖子时会收到多个帖子。幸运的是,我们只对最后一篇文章感兴趣,所以当来自同一表单的新请求出现时,我们需要一种中止\取消正在进行的请求的

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

一切正常,但如果use很快开始点击复选框(这在我们的场景中是正常情况),controller在处理之前的帖子时会收到多个帖子。幸运的是,我们只对最后一篇文章感兴趣,所以当来自同一表单的新请求出现时,我们需要一种中止\取消正在进行的请求的方法

我尝试的是: 1-当服务器仍在处理前一个帖子时,阻止客户端进行多个帖子。这是不可取的,因为它会在浏览器端造成明显的停顿

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

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

有什么想法吗?

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


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

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

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

实际上,您可能会遇到以下情况:

  • 客户端发送请求
  • 服务器开始处理请求B
  • 客户端发送请求B
  • 服务器开始处理请求B
  • 服务器返回请求B的结果,客户端也相应地进行了更改
  • 服务器返回请求A的结果,客户端相应地进行更改(并撤消请求B之前的更改)

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

    感谢您的帮助@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;
                }
            }
        }
    }
    

    您应该考虑实现客户端解决方案,以消除回发到控制器的持续需要。当用户保存或其他触发器时,SPA可以发回整个对象状态。从C#到Javascript的代码不可能超过2400行。我们在C#中拥有所有逻辑,包括验证和数据耦合。另外,我们的解决方案不是SPA,即使在这种情况下它看起来像SPA行为。我很好的方法是构建一个反弹系统,假设您有一个命令数组,因此每次更改复选框时,您都会存储此操作,并且您有一个每秒运行的超时函数,此函数将查找数组中的最后一个元素,执行请求并清除它。这样,在最后一次操作中,每分钟只有一个请求
            [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;
        }
    
            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;
            })
        }