【问题标题】:How to handle / cancel concurrent request by same user asp.net如何处理/取消同一用户asp.net的并发请求
【发布时间】:2009-11-05 09:20:45
【问题描述】:

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

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

这一定是一个很常见的问题,但我找不到任何关于此的好信息。 asp.net 中是否有任何设置会在最新之前取消所有请求,还是我必须为此实施自己的系统? 有没有针对这种情况的最佳做法?

提供一个更常见的有效示例: 假设一个页面请求对一个 sql server 做了 5 次选择,一个用户以 20 次超快的速度点击 F5。执行 5*20 选择是我想要避免的,可能会执行 10 次选择,因为像这样按 F5 需要一些时间,但是一旦有请求的建立,只有最后一个应该被执行。

提前致谢!

更新/附加信息:

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

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

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

【问题讨论】:

    标签: c# asp.net


    【解决方案1】:

    鉴于您施加的限制,我会做这样的事情来序列化给定用户的后端访问:

    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);
       }
    

    【讨论】:

      【解决方案2】:

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

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

      【讨论】:

        【解决方案3】:

        难道你不能只在你的后端应用程序中缓存数据库查询的结果(在你已经缓存连接的同时),并且如果一个相同的请求进来返回缓存的结果而不重新查询你的数据库?

        【讨论】:

          【解决方案4】:

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

          【讨论】:

            【解决方案5】:

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

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

            但是如果他从另一个窗口尝试 - 在这种情况下,该请求需要得到服务,因为它没有严格地提出。

            我认为不可能 彻底解决问题。而我的 这需要在 IIS 中注意 级别而不是 ASP.Net。

            【讨论】:

              【解决方案6】:

              听起来你快到了 - 但我会做更多的事情来阻止使用 F5

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

              1 中断请求

              添加一个会话变量,例如InProgress,在您开始执行选择命令时将其设置为true,并在完成后设置为false

              然后您可以在开始操作之前检查此值 - 如果它已经是 InProgress,请退出,通知用户他们已快速按 F5,请稍候再试一次。

              显然,您应该在开始时警告用户这是一个长期运行的过程,以及他们的行为的后果 - 尽管我们都知道用户不会阅读警告。

              2 向用户提供更多反馈

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

              【讨论】:

                【解决方案7】:

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

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

                【讨论】:

                  【解决方案8】:

                  昨天晚上晚些时候,我通过使用与 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 等。

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-09-27
                    • 2016-05-05
                    • 2020-02-15
                    • 1970-01-01
                    • 1970-01-01
                    相关资源
                    最近更新 更多