Quantcast
Viewing all articles
Browse latest Browse all 2367

AJAX 網頁踩雷記:ASP.NET MVC 一秒變蝸牛

來看一個有趣實驗。

以下是個簡單的 ASP.NET MVC Controller,在 Index View 透過 AJAX 呼叫向 Server 讀取資料,SimuAjaxCall 則模擬 AJAX 呼叫動作,使用 Thread.Delay() 延遲指定秒數後傳回字串結果:

using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
 
namespace LabWeb.Controllers
{
publicclass HomeController : Controller
    {
public ActionResult Index()
        {
return View();
        }
public ActionResult SimuAjaxCall(int seqNo, int delay)
        {
            System.Threading.Thread.Sleep(delay * 1000);
return Content($"AjaxCall-{seqNo}");
        }
    }
}

Index.cshtml 網頁內容如下。有個測試按鈕觸發同步發出 7 個 AJAX 呼叫 SimuAjaxCall,並將每次呼叫取得內容顯示在網頁上。先聲明,這並非良好的設計方式,依據 HTTP 規範,瀏覽器對同一網站來源的同時連線數有其上限,預設為 6 條,故第 7 個 AJAX 請求必須等待前 6 個請求有人執行完畢後才會送出,故設計時應盡可能透過合併或其他技巧,減少 AJAX Request 數目。(關於連線數上限議題,可參考這篇文章)網頁上還有另一顆「變蝸牛」按鈕,背後呼叫 /Magic/Snail 取得字串顯示,至於它背後做了什麼事,在此先賣個關子。

@{
    Layout = null;
}
 
<!DOCTYPEhtml>
 
<html>
<head>
<metaname="viewport"content="width=device-width"/>
<title>Multiple AJAX Call Test</title>
</head>
<body>
<div>
<buttonid="btnAjax">測試 AJAX 呼叫</button>
<buttonid="btnSnail">變蝸牛</button>
<ul>
</ul>
</div>
<scriptsrc="https://code.jquery.com/jquery-3.2.1.js"></script>
<script>
        $("#btnAjax").click(function () {
//發出7個耗時1秒的AJAX呼叫
for (var i = 0; i < 7; i++) {
                $.post("/Home/SimuAjaxCall", { seqNo: i, delay: 1 })
                    .done(function (res) {
                        $("ul").append("<li>" + res + "</li>");
                    });
            }
        });
        $("#btnSnail").click(function () {
            $.post("/Magic/Snail")
                .done(function (res) {
                    $("ul").append("<li>" + res + "</li>");
                });
        });
</script>
</body>
</html>

我們的測試方法是先按「測試AJAX呼叫」,用 F12 開發者工具觀察 7 個 AJAX Request 的執行時間,接著使用「變蝸牛」魔法捲軸,之後再按一次「測試AJAX呼叫」觀察結果差異。實測結果如下:

Image may be NSFW.
Clik here to view.

第一次 7 個 AJAX Request 齊發測試(黃色部分)一如預期,前六個同步執行,第七個等了 1 秒才執行(1 秒綠色長條前方有 1 秒的灰色細長條為等待時間),驗證了瀏覽器對同一站台同時連線上限數為 6。

呼叫 /Magic/Snail 後再做一次相同測試,結果卻截然不同,七個 AJAX Request 分別花了 1 到 8 秒才執行完(紅色部分)!若使用者必須等待全部 AJAX Request 完成,等待時間也由 2 秒拉長到 8 秒。

這情境似曾相識,對吧?(感覺陌生的同學可參考 再探ASP.NET大排長龍問題

是的,揭曉 /Magic/Snail 裡的魔法,就是 Session!

using System.Web.Mvc;
 
namespace LabWeb.Controllers
{
publicclass MagicController : Controller
    {
public ActionResult Snail()
        {
            Session["A"] = 123;
return Content("變蝸牛!");
        }
    }
}

有趣的實驗,但發生在真實環境我可笑不出來… (補聲暗)

當時我遇到的狀況是網頁同時發出十多個 AJAX Request,前六個 AJAX 呼叫每個耗時 5-12 秒,但個別執行明明只要 1-3 秒。尤其某個應該瞬間完成的 Action,我在 Action 第一行跟最後一行寫 Log 記錄執行時間,發現 Action 從開始到結束花不到 0.1 秒,但 IIS Log 記錄跟瀏覽器端觀察到執行時間都在 4 秒以上,推測時間耗消在呼叫 MVC Action 之前或 Action 完成之後,卻又無從調查。同事 J 提醒可能與 Session 有關,這才恍然大悟。萬萬沒想到,原本以為不用 WebForm 就再也不用擔心 Session 阻塞交通,但事實不然…

一旦你在網站應用程式的某個角落用了 Session,MVC Action 也會大排長龍,一秒變蝸牛!

追究原因,程式用了某個 WebForm 時代的古老元件,其中使用 Session 保存狀態。傳統 WebForm 以 PostBack 為主,Session 的鎖定行為影響有限,當應用在會同時發出多個 AJAX Request 的場景,便導致了可怕的後遺症。 

解決方法很簡單,有同步 AJAX 執行需求且要避免被 Session 摧毁效能的 Controller 上請加註[SessionSate()],設成 Disabled 或 ReadOnly:(前題是這個 Controller 未使用 Session 或對 Session 只讀不寫)

    [SessionState(System.Web.SessionState.SessionStateBehavior.Disabled)]
publicclass HomeController : Controller

修正後,Action 同步呼叫不再變蝸牛。

Image may be NSFW.
Clik here to view.

狠狠地被上了一課!如果你的網站採取 AJAX 方式設計,Session 這種活化石,就別再用了。

Image may be NSFW.
Clik here to view.

Viewing all articles
Browse latest Browse all 2367

Trending Articles