为控制器操作设置超时

时间:2011-05-31 08:16:36

标签: c# jquery asp.net-mvc-2 azure timeout

我已经遇到this thread,但我可能还需要其他一些东西。

我有一个返回ViewResult的操作,该操作由客户端的$.post()

调用

JavaScript的:

var link = 'GetFoo?fooBar=' + fooBar;

var jqxhr = $.post(link, function (response) {
    $('#myDiv').replaceWith(response);
});

控制器:

public ViewResult GetFoo(String fooBar)
{
    if (Request.IsAjaxRequest())
    {
        // perform a ridiculously long task (~12 minutes)           
        // algorithm: 1) download files from the Azure blob storage
        // 2) update each file
        // 3) reupload to blob storage
        // 4) return a list of URIs to be displayed to the UI
        return View("MyFooView", data);
    }
    throw new InvalidOperationException();
}

正如评论所暗示的那样,Controller内部有一个长任务。 (这是一个文档生成模块,可将PDF上载到Azure blob存储,并将链接返回到View。)

这在我的开发机器上工作正常,但是当它在(安全)Azure生产环境中运行时,它超时。我已经在各处输入了大量的日志记录条目,结果证明,它能够上传文档并返回控制器(即它到达上面的控制器返回语句)。但是,当需要将模型数据返回到View时,客户端脚本不会回调(即div内容不会被结果替换。)

有没有办法以某种方式延长电话的超时?很难在我的(不安全的)本地环境中重现,因此明确的修复将有所帮助。

如果我在[AsyncTimeout(3600)]方法上使用属性GetFoo(),则永远不会从UI调用此操作。

任何建议都将受到赞赏。

3 个答案:

答案 0 :(得分:3)

问题是Azure负载均衡器有自己的超时,设置为一分钟。任何需要更长时间than a minute gets terminated的请求。没有办法改变这一点。

在Azure环境中解决这个问题的方法是让一个ajax调用启动进程并返回某种进程ID,然后让客户端调用另一个ajax调用来传入此进程ID以查看它是否完整。它可能看起来像这个未编译和未经测试的代码。在javascript中:

var link = 'BeginFooProcessing?fooBar=' + fooBar;

var jqxhr = $.post(link, function (response) {
    var finishedlink = 'CheckFooFinished?fooId=' + response;

    // Check to see if we're finished in 1 second
    setTimeout("CheckIfFinishedYet('" + finishedlink + "')", 1000);
});

function CheckIfFinishedYet(finishedlink) {
    var response = $.post(finishedlink, function (response) {
        if (response == null) {
            // if we didn't get a result, then check in another second
            setTimeout("CheckIfFinishedYet('" + finishedlink + "')", 1000);
        }
        else {
            // Yay, we've got a result so we'll just write it out
            $('#myDiv').replaceWith(response);
        }
    });
}

在你的控制器中:

public ViewResult BeginFooProcessing(String fooBar)
{
    if (Request.IsAjaxRequest())
    {
        Guid fooId = Guid.NewGuid();

        var result = new FooResult
                        {
                            FooId = fooId,
                            HasFinishedProcessing = false,
                            Uris = new List<string>()
                        };

        // This needs to go to persistent storage somewhere
        // as subsequent requests may not come back to this
        // webserver
        result.SaveToADatabaseSomewhere();

        System.Threading.Tasks.Task.Factory.StartNew(() => ProcessFoo(fooId));


        return View("MyFooStartView", fooId);
    }
    throw new InvalidOperationException();
}

private void ProcessFoo(Guid fooId)
{
    // Perform your long running task here

    FooResult result = GetFooResultFromDataBase(fooId);

    result.HasFinishedProcessing = true;
    result.Uris = uriListThatWasCalculatedAbove;

    result.SaveToADatabaseSomewhere();
}

public ViewResult CheckFooFinished(Guid fooId)
{
    if (Request.IsAjaxRequest())
    {
        FooResult result = GetFooResultFromDataBase(fooId);

        if (result.HasFinishedProcessing)
        {
        // Clean up after ourselves
        result.DeleteFromDatabase();

            return View("MyFooFinishedView", result.Uris);
        }

        return View("MyFooFinishedView", null);

    }
    throw new InvalidOperationException();
}

private class FooResult
{
    public Guid FooId { get; set; }

    public bool HasFinishedProcessing { get; set; }

    public List<string> Uris;
}

希望这会给你一个起点。

答案 1 :(得分:2)

你想看看这个

回答你的问题是:[AsyncTimeout(3600000)]

在这里看到更多 Controller Timeout MVC 3.0

答案 2 :(得分:1)

要使用Async控制器,您的控制器必须从AsyncController继承:

public class WebsiteController : AsyncController

然后使用异步方法的任何Action都必须使用

的格式
public void ActionNameAsync(int param)
public ActionResult ActionNameCompleted(int param)

其中ActionName是您的操作的名称,而不是Async函数使用

AsyncManager.OutstandingOperations.Increment();

每次你开始一个新的aysnchronous方法和

AsyncManager.OutstandingOperations.Decrement();

当方法完成后,在所有未完成的操作完成后,它将继续移动到Completed函数(确保在async函数中指定完成函数所需的参数,以便它知道要传递的内容)

AsyncManager.Parameters["param"] = "my name";

然后使用AsyncTimeout属性实际上会影响该函数。如果您尝试将该属性应用于不在异步控制器中的操作,我不确定会发生什么。

这些更改不需要更改javascript中对该操作的任何引用,而不是因为您仍然只是请求'ActionName',它会查看是否存在Async / Completed安装版本以及是否不是它只是寻找那个正常的动作并使用它找到的任何一个。