关于C#:如何处理/取消同一用户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服务器,则在"版本"系统中仍然会有好处。

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


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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
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);
     }
}

然后在页面代码中:

1
2
3
4
5
6
7
8
   // 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);
   }

我昨天晚间通过使用某种类似于Gonzalo的方法解决了这个问题,但是该方法具有一些结束"绝对"请求的功能。
现在我可以一直按住F5并在释放F5后的1秒钟内仍然得到有效的响应:) :)到目前为止,我还没有看到"请不要刷新得这么快!"。客户端上的消息。

我创建了此类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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()一起使用,完美无瑕

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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。


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

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


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

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

1中断新请求

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

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

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

2向用户提供更多反馈

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


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

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

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

I think it is not possible to
completely fix the problem. And my be
this needs to be taken care at the IIS
level and not by ASP.Net.


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


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


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

在想知道是否刷新该页面时,ASP.NET知道不需要完成上一页。但这并不能解决您眼前的问题,但是可以解决的问题很小。