技术开发 频道

ASP.NET AJAX之内部揭秘

    糟糕的调用会使好的调用超时

    如果有两个HTTP调用不知何故执行了很长的时间,那么这两个糟糕的调用也将会使好的调用超时,同时这两个调用会进入队列。这里就有一个例子:
function TestTimeout()
{
debug.trace("--Start--");
TestService.set_defaultFailedCallback(
function(result, userContext, methodName)
{
var timedOut = result.get_timedOut();
if( timedOut )
debug.trace( "Timedout: " + methodName );
else
debug.trace( "Error: " + methodName );
});
TestService.set_defaultSucceededCallback( function(result)
{
debug.trace( result );
});

TestService.set_timeout(5000);
TestService.HelloWorld("Call 1");
TestService.Timeout("Call 2");
TestService.Timeout("Call 3");
TestService.HelloWorld("Call 4");
TestService.HelloWorld("Call 5");
TestService.HelloWorld(null); // 这句将导致错误
}
    服务端的web service也非常简单:
[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ScriptService]
public class TestService : System.Web.Services.WebService {

public TestService () {

// 如果使用设计的组件,请取消注释以下行
// InitializeComponent();
}

[WebMethod][ScriptMethod(UseHttpGet=true)]
public string HelloWorld(string param) {
Thread.Sleep(1000);
return param;
}

[WebMethod][ScriptMethod(UseHttpGet=true)]
public string Timeout(string param) {
Thread.Sleep(10000);
return param;
}
}
    我调用了服务端的名为“Timeout”的方法,它不会做任何事情,而只是等待一个较长的时间以使调用超时。之后,我再调用一个不会超时的方法。但是你猜猜输出的是什么:



    只有第一个调用成功了。所以,任何时候如果浏览器的两个连接都处在拥堵状态的话,那么你期待的其他调用也都将会超时。
    在Pageflakes的运营中,我们曾经几乎每天都从客户端得到400个到600个超时错误报告,我们从未发现这是怎么发生的。起初,我们怀疑是互联网连接过慢造成的,但是不可能如此多的用户都发生这种情况。后来,我们猜测是主机提供商的网络出现了问题。我们做了大量的网络析去发现问题是否是出现在网络上,但是我们没有发现任何异常。我们使用了SQL Profiler去查找是否是长时间运行的查询导致了ASP.NET请求执行时的超时。但是不幸,我们最终发现的是,大部分超时错误出现的情况都是先有一些坏的调用,然后好的调用也超时了。所以我们修改了Atlas运行时,引进了自动重试的功能,问题终于完全消失了。然而,自动重试需要对ASP.NET AJAX框架的Javascript做一次“心脏外科手术”,这个方法会要求每个调用在超时后都重试一次。为了实现它,我们需要截获所有web method调用并且在onFailure回调函数中作个钩子,如果失败的原因是超时,onFailure将再次调用相同的web method

    另一个需要关注的发现是当我们外出旅行感到疲惫时,想通过酒店或机场的无线网络连接到互联网访问Pageflakes的时候,首次访问总是不成功,并且所有的web service调用在第一次尝试中总是失败。直到我们刷新之前都不会工作。这也是我们要实现web service调用立即自动重试的另一个主要原因,它正好可以解决这个问题。

    这里我会告诉你怎么做。Sys$Net$WebServiceProxy$invoke函数是负责处理所有web service调用的。所以,我们需要通过一个自定义onFailure回调函数来替换这个函数。只要有错误或者超时就会激发这个自定义回调函数。所以,当有超时发生的时候,就会再次调用这个函数,重试就会发生。
Sys.Net.WebServiceProxy.retryOnFailure =
function(result, userContext, methodName, retryParams, onFailure)
{
if( result.get_timedOut() )
{
if( typeof retryParams != "undefined" )
{
debug.trace("Retry: " + methodName);
Sys.Net.WebServiceProxy.original_invoke.apply(this, retryParams );
}
else
{
if( onFailure ) onFailure(result, userContext, methodName);
}
}
else
{
if( onFailure ) onFailure(result, userContext, methodName);
}
}

Sys.Net.WebServiceProxy.original_invoke = Sys.Net.WebServiceProxy.invoke;
Sys.Net.WebServiceProxy.invoke =
function Sys$Net$WebServiceProxy$invoke(servicePath, methodName, useGet,
params, onSuccess, onFailure, userContext, timeout)
{
var retryParams = [ servicePath, methodName, useGet, params,
onSuccess, onFailure, userContext, timeout ];

// 初始调用失败
// 处理自动重试
var newOnFailure = Function.createDelegate( this,
function(result, userContext, methodName)
{
Sys.Net.WebServiceProxy.retryOnFailure(result, userContext,
methodName, retryParams, onFailure);
} );

Sys.Net.WebServiceProxy.original_invoke(servicePath, methodName, useGet,
params, onSuccess, newOnFailure, userContext, timeout);
}
    运行的时候,它将把每个超时调用都重试一次



    这里你可以看到第一个方法成功了,所有其他超时的调用都会被重试。而且你也会看到重试一次后的调用都成功了。发生这种情况是因为在重试中服务端的方法不会做超时处理。所以,这证明了我们的实现方法是正确的。

0
相关文章