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

时间:2009-11-05 09:20:46

标签: c# asp.net

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

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

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

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

提前致谢!

更新/其他信息:

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

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

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

8 个答案:

答案 0 :(得分: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);
   }

答案 1 :(得分:0)

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

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

答案 2 :(得分:0)

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

答案 3 :(得分:0)

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

答案 4 :(得分:0)

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

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

但是,如果他从另一个窗口尝试怎么办?在这种情况下,需要提供请求,因为它不是严格要求的。

  

我认为不可能   完全解决问题。而我是   这需要在IIS处理   等级,而不是ASP.Net。

答案 5 :(得分:0)

听起来你几乎就在那里 - 但我会做更多的事情来阻止使用 F5

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

1分请求

在您开始执行select命令时添加您设置为InProgress的会话变量,例如true,并在完成后设置为false

然后您可以在开始操作之前检查此值 - 如果它已经InProgress,纾困,告知用户他们已快速点击 F5 ,请等待再试一次

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

2向用户提供更多反馈

你需要看看你的用户为什么如此按 F5 - 通常由于网络缺乏响应而感到沮丧 - 人们已经习惯了AJAXy的做事方式 - “进行中”类型动画通知他们发生的事情,请等待“请不要打F5”类型的消息通常有效。您需要考虑使用类似UpdatePanel或其他JS库的东西来帮助那里。

答案 6 :(得分:0)

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

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

答案 7 :(得分:0)

我昨天晚上通过使用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;
    }
}

并像这样使用它和添加的锁()一起完美无缺

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。