当新请求到达ASP.NET MVC 5时,如何中止旧请求处理?
我有一个包含数百个复选框和下拉菜单的表单(其中许多复选框和下拉菜单的值是耦合在一起的)。在操作中,有更新机制来更新会话中的对象。此对象执行所有值的验证和耦合,例如,如果用户在一个输入字段中键入%50,我们可能会在下拉列表中添加3个新的SelectListItem 一切正常,但如果use很快开始点击复选框(这在我们的场景中是正常情况),controller在处理之前的帖子时会收到多个帖子。幸运的是,我们只对最后一篇文章感兴趣,所以当来自同一表单的新请求出现时,我们需要一种中止\取消正在进行的请求的方法 我尝试的是: 1-当服务器仍在处理前一个帖子时,阻止客户端进行多个帖子。这是不可取的,因为它会在浏览器端造成明显的停顿 2-有几种通过使用哈希代码或AntiForgeryToken阻止多个回发的解决方案。但它们不是我所需要的,我需要中止正在进行的线程以支持新的请求,而不是阻止传入的请求 3-我尝试通过添加两个消息处理程序(一个在操作之前,另一个在执行操作之后)来扩展管道,以保留哈希代码(或AntiForgeryToken),但问题仍然存在,即使我可以检测到有正在处理同一请求的线程,我也无法中止该线程或将旧请求设置为完成 有什么想法吗?您唯一能做的就是限制客户端的请求。基本上,单击复选框时需要设置超时。您可以让该初始请求通过,但随后任何进一步的请求都将排队(或在场景中的第一个排队请求之后实际丢弃),并且在超时清除之前不运行该请求当新请求到达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在处理之前的帖子时会收到多个帖子。幸运的是,我们只对最后一篇文章感兴趣,所以当来自同一表单的新请求出现时,我们需要一种中止\取消正在进行的请求的
无法中止服务器端的请求。每个请求都是幂等的。对之前或之后发生的任何事情都没有固有的认识。服务器有多个线程来处理请求,并将尽可能快地处理这些请求。如何处理请求或如何发送响应没有顺序。第一个请求可能是接收响应的第三个请求,这仅仅取决于每个请求的处理方式。您试图通过异步技术实现事务功能(即仅计算最后一个请求)。这是一个设计缺陷 由于您拒绝在客户端进行阻止,因此无法控制先处理哪些请求,或者在客户端再次正确处理结果 实际上,您可能会遇到以下情况:
阻塞是确保正确顺序的唯一方法。感谢您的帮助@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;
})
}