繁体   English   中英

如何处理/取消同一用户asp.net的并发请求

[英]How to handle / cancel concurrent request by same user asp.net

我正在编写一个asp.net应用程序。 当用户查看页面XXX.aspx时,会对提供数据和业务逻辑的后端应用程序进行大量调用。 我有一个来自该后端应用程序的API,可用于调用方法以执行业务逻辑并获取数据。

我的问题如下:如果用户反复按下F5(甚至只是按住F5),那么将同时执行多个Web请求。 这再次迫使我打开与后端应用程序的多个连接。 打开连接非常昂贵,因此我实现了一种缓存机制,以便用户会话获得连接并坚持使用该连接,直到15秒钟后将其返回到连接池。 如果激活缓存机制,则在我快速按下F5时,与后端应用程序的连接会崩溃。 发生这种情况是因为所有请求都在同一时间处理,因此尝试同时使用同一连接。 这不是“合法的”。 够了:)我添加了一个睡眠功能,以便在任何给定时间仅单个请求可以使用连接。 但这又很慢,如果我击中F5 20次,我将必须等待大约15-20秒,然后才能显示“最后”响应。 我不想处理所有这些请求。 我曾尝试在Web上的其他asp.net应用程序上按住F5键,但我注意到有些出现此问题,而其他则没有。

这肯定是一个非常普遍的问题,但是我找不到关于此的任何好的信息。 asp.net中是否有任何设置可以取消所有最新请求,还是我必须为此实现自己的系统? 围绕这种情况是否有最佳实践?

提供一个更常见的有效示例:假设一个页面请求对一个sql服务器执行了5次选择,而用户击中F5的速度是20倍。 我要避免执行5 * 20的选择,可能会执行10个选择,因为这样需要花费一些时间才能击中F5,但是一旦出现请求积累,则只应执行最后一个。

提前致谢!

更新/其他信息:

  • 内容“无法”被缓存。 同样默认情况下,任何缓存都应该/应该在后端系统中完成,从而使分布式缓存成为可能,并使缓存对后端系统之上运行的其他应用程序可用。
  • 每个会话都有自己的后端连接。
  • 该页面“快速”,加载时间为750-1000ms。 (但是我在推动F5方面要快得多...)

如果没有更好的解决方案出现,我将在会话对象或类似对象中创建一个版本号。 在对后端进行任何呼叫之前,将当前会话的版本号与会话中的版本号进行比较。 仅执行与最新版本号匹配的请求。 对于我的ajax页面,此检查将被跳过,因此可以进行并发ajax调用。 也可能是我将在asp.net中求助于某种非常短暂的缓存,但它打开了一个全新的问题世界。 毕竟这是很少会出现的问题,而且由于我将asp.net解决方案设计为或多或少是无状态的,因此不可能完全禁止它。 但是,如果有2个甚至更多的Web服务器,“版本”系统仍然会受益。

到目前为止,感谢您的良好建议和有趣的意见!

鉴于您施加的限制,我将执行以下操作来序列化给定用户的后端访问:

Connection AcquireConnection (string user)
{
      lock (user) {
          Cache cache = HttpRuntime.Cache;
          string key = user + "@@@ConnectionInUse";
          if (cache [key] != null) {
               Monitor.Wait (user, true); // TODO: check return value!
          }
          cache [key] = key;
          return OpenConnection ();
      }
}

void ReleaseConnection (string user)
{
     lock (user) {
         Cache cache = HttpRuntime.Cache;
         string key = user + "@@@ConnectionInUse";
         cache.Remove (key);
         Monitor.Pulse (user);
     }
}

然后在页面代码中:

   // This will block until there's no other connection in use for 'user'
   Connection cnc = AcquireConnection (user);
   try {
       // Do your thing here
   } finally {
         // This will wake up the next request in Monitor.Wait()
         ReleaseConnection (user);
   }

我不确定这是否行得通,但我只是想:在进行昂贵的操作之前,请尝试使用Response.IsClientConnected

只是想知道,刷新该页面后,ASP.NET是否知道不需要完成上一页。 那并不能解决您眼前的问题,但是可以作为一个小解决方法。

您是否不仅可以将数据库查询的结果缓存在后端应用程序中(对于已经缓存连接的同一时间),并且是否有相同的请求返回而无需重新查询数据库就返回缓存的结果?

我认为您需要使用延迟初始化值来调用后台和服务。 如果有任何客户端请求信息A ,则您将阻止该客户端,并在单独的后台线程调用后端服务中。 所有后续请求都将被相同的惰性值阻止。 当信息可用时,惰性值存储在缓存中,所有后续请求都将拥有它。

这与数据库无关。 您需要注意用户将严格按F5的行为。

您可以做的一件事是创建一个会话密钥,以跟踪客户端请求。 该页面不会再收到来自同一客户端的其他请求。 该过程完成后,您释放该密钥并等待接受新的请求。

但是,如果他从另一个窗口尝试尝试该怎么办?在这种情况下,由于请求没有经过严格的处理,因此需要处理。

我认为不可能完全解决问题。 我要在IIS级别而不是ASP.Net上注意这一点。

听起来好像快要到了,但是我会做更多的事情来阻止F5的使用。

由于您已经在通过会话的使用来跟踪用户,以将他们的请求限制为一个连接,因此您应该尝试鼓励他们更加耐心。 有两种方法可以执行此操作:

1中断请求

添加一个会话变量如InProgress你设置为true ,当你开始进行你的选择命令,并设置为false ,当你完成。

然后,您可以在开始操作之前检查该值-如果它已经处于InProgress ,请纾困,并通知用户他们已快速按下F5 ,请等待重试。

显然,您应该从一开始就警告用户这是一个长期运行的过程,并且会对他们的行为产生影响-尽管我们都知道用户不会阅读警告。

2向用户提供更多反馈

您需要查看用户为何如此频繁地按F5键 -通常是由于缺乏网络响应而感到沮丧-人们逐渐习惯了AJAXy的工作方式-一种“进行中”类型的动画通知他们这些东西正在发生,请等待“请不要按F5”类型的消息,通常可以正常工作。 您需要研究使用诸如UpdatePanel之类的东西或其他一些JS库来提供帮助。

在具有大量动态内容的页面上,我仍然将输出至少转储到缓存中至少15秒钟,这样,如果重复的刷新请求命中,则不必每次都到达后端。 即使您的后端为您缓存了该缓存,它的15秒缓冲区也意味着您每分钟最多只能沉迷4次内容。

接下来要做的是实现一个阻塞方案,该方案可以像Session["IsLoading"] = true一样简单,除非该值不存在或为false,否则不进行新的后端连接。 如果处于加载状态,它将返回一条简单的“让我独自工作”的消息,该消息还会导致页面在2或3秒后自动刷新。

我昨天晚上用一个与Gonzalo相似的方法解决了这个问题,但是它有一些额外的功能可以终止“绝对”请求。 现在,我可以一直按住F5并释放F5后1秒钟内仍然得到有效的响应:) :)到目前为止,我还没有看到“请不要刷新得这么快!”。 客户端上的消息。

我创建了这个课程

public class RequestTracker
{   
    private Hashtable hash;
    private Hashtable hashSync;

    public RequestTracker() {
        hash = new Hashtable();
        hashSync = Hashtable.Synchronized(hash);
    }

    public int UpdateRequestId() {
        if (CwGlobal.SessionIdExists)
        {
            int newRequestId = hashSync.ContainsKey(CwGlobal.SessionId) ? (int)hashSync[CwGlobal.SessionId] + 1 : 1;
            hashSync[CwGlobal.SessionId] = newRequestId;
            return newRequestId;
        }
        return 0;
    }
    public int CurrentRequestId() {
        if(CwGlobal.SessionIdExists && hashSync.ContainsKey(CwGlobal.SessionId))
            return (int)hashSync[CwGlobal.SessionId];
        return 0;
    }
}

并像这样使用它,并与添加的lock()一起完美地工作

public void ValidateRequest()
{
    if(!this.UseRequestTracker || requestTracker == null)
        return;

    if (RequestId < requestTracker.CurrentRequestId())
    {
        httpContext.Response.Write("Please do not refresh so fast!");//TODO add automatic refresh to this page
        httpContext.Response.End();
    }
}
public string Foo()
{
    ValidateRequest();

    string ret;
    using (var apiConn = apiPool.getConn())
    {
        lock (apiConn)
        {
            ret = (string)apiConn.Call("bar");
        }
    }
    return ret;
}

还有更多代码,例如在Global.asax中添加RequestTracker,并确保在页面加载时设置RequestId等。

暂无
暂无

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

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