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
{
    public class 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;
}
 
<!DOCTYPE html>
 
<html>
<head>
    <meta name="viewport" content="width=device-width" />
    <title>Multiple AJAX Call Test</title>
</head>
<body>
    <div> 
        <button id="btnAjax">測試 AJAX 呼叫</button>
        <button id="btnSnail">變蝸牛</button>
        <ul>
        </ul>
    </div>
    <script src="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呼叫」觀察結果差異。實測結果如下:

第一次 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
{
    public class 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)]
    public class HomeController : Controller

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

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

歡迎推文分享:
Published 07 June 2017 07:53 AM 由 Jeffrey
Filed under:
Views: 11,457



意見

# Spider said on 06 June, 2017 08:15 PM

請問大大不用session, 如何記住當前登入的user?我用的是webform

# Jeffrey said on 07 June, 2017 07:03 PM

to Spider, 這是這兩天詢問度頗高的疑問,我準備另寫一篇較完整的解釋,敬請期待。

你的看法呢?

(必要的) 
(必要的) 
(選擇性的)
(必要的) 
(提醒: 因快取機制,您的留言幾分鐘後才會顯示在網站,請耐心稍候)

5 + 3 =

搜尋

Go

<June 2017>
SunMonTueWedThuFriSat
28293031123
45678910
11121314151617
18192021222324
2526272829301
2345678
 
RSS
創用 CC 授權條款
【廣告】
twMVC
最新回應

Tags 分類檢視
關於作者

一個醉心技術又酷愛分享的Coding魔人,十年的IT職場生涯,寫過系統、管過專案, 也帶過團隊,最後還是無怨無悔地選擇了技術鑽研這條路,近年來則以做一個"有為的中年人"自許。

文章典藏
其他功能

這個部落格


Syndication