﻿# ASP.NET MVC 開發心得分享 (22)：關於 executionTimeout

當我們想要限制或加長 ASP.NET 可執行的時間長度時，通常都會到 web.config 的 <system.web> 區段新增一個 httpRuntime 元素並且指派 executionTimeout 屬性一個秒數，像是我們在設定檔案上傳的程式時，由於上傳檔案的執行時間可能會超過系統的預設值( 110 秒 )，所以這時我們就必須把這個數值調大。當然你也可以將這個數值縮小，以免過多、過長的執行要求把伺服器拖垮。不過，在 ASP.NET MVC 裡有一個鮮為人知的秘密，那就是 ASP.NET MVC 根本不吃這套，預設執行時間是沒有上限的，所以你的 ASP.NET MVC 程式要是出問題，那可是會執行到天荒地老海枯石爛的，最慘的狀況就是 IIS 的 Request Queue 被塞爆。

首先，我先標示出 <httpRuntime executionTimeout="300" /> 的使用範例，如下圖示：  
**請注意**：設定 executionTimeout 時，其 compilation 的 debug 屬性也必須設為 false 才有用。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f9c96b033ab3050cc8d2-image_1074.png)

由於從 ASP.NET MVC 2.0 開始，ASP.NET MVC 的 [MvcHandler](http://msdn.microsoft.com/zh-tw/library/system.web.mvc.mvchandler.aspx) 就改用 [IHttpAsyncHandler](http://msdn.microsoft.com/zh-tw/library/system.web.ihttpasynchandler.aspx) 來實做，所以所有透過 ASP.NET MVC 接收到的 Requests 都會以非同步的方式來執行，又基於「某些」不明原因所以不加上 Timeout 的限制（原因釐清中…）。

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/18966d364b38da56d65f-image_1075.png)

如果要解決這個問題，可能就必須要耍一點點小小的手段，才能讓你的 Controller 在執行時 Action 方法時能夠有個基本的 Timeout 時間，以下為設計為 Base Controller 的程式碼範例：  
**備註1**：以下程式碼只會針對 Action 的執行時間設定 Timeout，並不會計算 View 所執行的時間！  
**備註2**：以下程式碼會終止目前執行中的 Thread 運作，由於 ASP.NET MVC 使用非同步 Handler 的關係，同一個 Thread.CurrentThread 可能會同時服務好幾個不同的 Requests，所以強制 Abort 這個 Thread 很有可能會導致其他使用相同 Thread 的 Requests 也被中斷作業，並非為最佳解法！ \[ [參考討論](http://forums.asp.net/p/1715081/4590486.aspx) \]

```csharp
using System;
using System.Web.Mvc;
using System.Threading;

namespace MvcApplication3.Controllers
{
    public class TimeoutController : Controller
    {
        // Controller Timeout ( seconds )
        private int _controllerTimeout = 60;

        private bool _isExecuting = false;
        private Thread _executingThread;
        private readonly object _syncRoot = new object();

        protected override void ExecuteCore()
        {
            _executingThread = Thread.CurrentThread;
            ThreadPool.QueueUserWorkItem(o =>
            {
                Thread.Sleep(_controllerTimeout * 1000);
                if (_isExecuting)
                {
                    _executingThread.Abort();
                }
            });
            base.ExecuteCore();
        }

        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            _isExecuting = true;
            base.OnActionExecuting(filterContext);
        }

        protected override void OnActionExecuted(ActionExecutedContext filterContext)
        {
            _isExecuting = false;
            base.OnActionExecuted(filterContext);
        }

        public int ControllerTimeout
        {
            get
            {
                int retVal;
                lock (_syncRoot)
                {
                    retVal = _controllerTimeout;
                }
                return retVal;
            }
            set
            {
                lock (_syncRoot)
                {
                    _controllerTimeout = value;
                }
            }
        }
    } 
}
```

以上 Base Controller 的使用方法也很簡單，直接繼承 TimeoutController 類別即可：

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/fa3685fdd8bea93723e9-image_1076.png)

事實上，還有另外一個更加暴力的解決辦法，那就是直接透過 .NET 內建的 Reflection 機制修改一個在 HttpContext 裡的 **\_timeoutState** 私有欄位，將其狀態值改成 **1** 也可以啟用 ASP.NET MVC 的 Timeout 設定，而這樣就會真的套用原本 ASP.NET Framework 裡 **executionTimeout** 的設定！

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/f2b21a5fb091db0c37de-image_1077.png)

以下是設定 **\_timeoutState** 私有欄位的方法，你只要在任意程式碼開始執行之前執行他即可：

```csharp
System.Web.HttpContext.Current.GetType()
    .GetField("_timeoutState", 
        System.Reflection.BindingFlags.Instance | 
        System.Reflection.BindingFlags.NonPublic)
    .SetValue(System.Web.HttpContext.Current, 1);
```

不過這樣的寫法會有個小問題，那就是這段程式碼只能執行在無法在 High Trust 的執行環境裡，像有些提供 ASP.NET 的虛擬主機商會特別把 ASP.NET 的執行環境調整為 Middle Trust 的環境，那你就無法使用這段程式碼了。不過我想大多數的網站都還是跑在 High Trust 的執行環境下，所以應該不太會遇到問題。

套用標準 ASP.NET 的 executionTimeout 設定必須在 web.config 將 debug 模式設定為 false 才會生效，如果你的 debug 模式設定為 true 的話，executionTimeout 本來就是無效的！

![](https://stwillblogassets.blob.core.windows.net/files/images/external/stwillblogassets.blob.core.windows.net/8e3223648965ce90cbf4-image_1078.png)

好多小細節，知道的越多越有趣，我經常就是悠游在這些雞毛蒜皮，但會把人搞瘋的事情上打轉，呵呵～

**相關連結**

-   [ASP.NET 當上傳檔案過大時任何回應瀏覽器的動作都無效](/post/2008/01/26/When-POST-content-exceed-maxRequestLength-any-HTTP-responses-are-useless)
-   [Bug in MVC3 - requests never time out. Works fine for aspx pages in same project](http://stackoverflow.com/questions/7198267/bug-in-mvc3-requests-never-time-out-works-fine-for-aspx-pages-in-same-project)
-   [web.config executionTimeout not working in ASP.NET MVC](http://forums.asp.net/p/1715081/4579754.aspx/1?Re+web+config+executionTimeout+not+working+in+ASP+NET+MVC)
-   [Response.End and Filters](http://forums.asp.net/t/1624919.aspx/1)
-   [Is there a way to make executionTimeout take effect while debugging?](http://stackoverflow.com/questions/2231015/is-there-a-way-to-make-executiontimeout-take-effect-while-debugging)
-   [TIPS-Server.ScriptTimeout & <httpRuntime executionTimeout >](http://blog.darkthread.net/blogs/darkthreadtw/archive/2009/09/15/853.aspx)
-   [ScriptTimeout and executionTimeout do not work](https://web.archive.org/web/20251113155203/http://www.velocityreviews.com/forums/t101706-scripttimeout-and-executiontimeout-do-not-work.html)
