我正在编写一个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>
提前致谢!
更新/其他信息:
如果没有出现更好的解决方案,我将在会话对象或类似对象中创建版本号。在对后端进行任何调用之前,将与当前请求版本号与会话中的版本号进行比较。仅执行与最新版本号匹配的请求。对于我的ajax页面,将跳过此检查,以便可以进行并发的ajax调用。 也许我会在asp.net中使用某种非常短暂的缓存,但它会打开一个全新的问题世界。这毕竟是一个很少会出现的问题,而且由于我正在设计asp.net解决方案或多或少是无状态的,所以不可能完全禁止。但是,如果有2个甚至更多的Web服务器,那么“版本”系统仍然会带来好处。
到目前为止,感谢您提供了很好的建议和有趣的意见!
答案 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 。
由于您已经通过使用会话来跟踪用户将其请求限制为一个连接,因此您应该尝试鼓励他们更耐心一点。有两种方法可以做到这一点:
在您开始执行select命令时添加您设置为InProgress
的会话变量,例如true
,并在完成后设置为false
。
然后您可以在开始操作之前检查此值 - 如果它已经InProgress
,纾困,告知用户他们已快速点击 F5 ,请等待再试一次
显然,您应该在开始时警告用户这是一个长时间运行的过程,以及他们的操作的影响 - 尽管我们都知道用户不会阅读警告。
你需要看看你的用户为什么如此按 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。