2015年8月21日 星期五

Long-running task in IIS with HostingEnvironment.QueueBackgroundWorkItem


 IIS   ASP.NET   QueueBackgroundWorkItem  

背景

.NET Framework 4.5.2 新增了 HostingEnvironment.QueueBackgroundWorkItem 方法。

MSDN 說明:
HostingEnvironment.QueueBackgroundWorkItem 方法可讓您排程小型背景工作項目。 完成所有背景工作項目之前,ASP.NET 會追蹤這些項目,並防止 IIS 突然終止背景工作處理序。 此方法只能在 ASP.NET Managed 應用程式網域內呼叫。

限制:

1.  The takeaway from this is reliably. If you use this new HostingEnvironment queue in an ASP.NET app, any background tasks that can complete execution within the 30 second graceful shutdown period are guaranteed to execute safely.
(Before IIS shuts down  ASP.NET gives 30 second to all the background processes running and registered with it to complete.) …
原文
2.  QueueBackgroundWorkItem limitations (MSDN) … 原文

更多說明請參考本篇的References
以下會實作此方法並實際測試在IIS的運作情況。

環境

l   Windows 7 Enterprise, Windows Server 2008 R2 Enterprise
l   IIS 7.5
l   Visual Studio 2015 Ent.
l   NLOG 4.0.1


Practice


Create a WebApi with long-running Task

先做一組對照組,在WebApiPOST method觸發一段每兩秒寫一次Log的執行緒。

public class ValuesController : ApiController
{
        // logger
        private static Logger logger = NLog.LogManager.GetCurrentClassLogger();

        // POST api/values
        [HttpPost]
        public HttpResponseMessage Post()
        {
            try
            {
                //Create a thread for long-running task
                Task.Run(() =>
                {
                    this.logRunningWriteLog();
                });
                return new HttpResponseMessage(HttpStatusCode.OK);
            }
            catch (Exception ex)
            {
                logger.Fatal(ex.Message);
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
               
            }
        }

        private void logRunningWriteLog()
        {
            for (int i = 0; i < 50; i++)
                {
                    logger.Info(String.Format("QBW Test-{0}", i));
                    Thread.Sleep(2000);
                }
        }
}


放上IIS後,當跑到第六次logging時,手動回收(或停止)IIS Application Pool



Thread的工作也馬上隨之停止。


HostingEnvironment.QueueBackgroundWorkItem

現在我們把工作用QBWI包裝起來執行~ 完整程式碼如下 ..
1.  HostingEnvironment.QueueBackgroundWorkItem 可支援
Func<CancellationToken, Task>
Action<CancellationToken>

2.  在工作進行階段,可隨時偵測CancellationToken,當IIS開始嘗試要停止Process時便可以立即做對應處理。

public class ValuesController : ApiController
{
        // logger
        private static Logger logger = NLog.LogManager.GetCurrentClassLogger();

        // POST api/values
        [HttpPost]
        public HttpResponseMessage Post()
        {
            try
            {
                //Use QueueBackgroundWorkItem to run the async long-running task
                Func<CancellationToken, Task> workItem = longRunningWriteLogAsync;
                HostingEnvironment.QueueBackgroundWorkItem(workItem);

                return new HttpResponseMessage(HttpStatusCode.OK);
            }
            catch (Exception ex)
            {
                logger.Fatal(ex.Message);
                return new HttpResponseMessage(HttpStatusCode.InternalServerError);
               
            }
        }

        private void logRunningWriteLog(CancellationToken cancellationToken)
        {
            bool isCancellationHadBeenTriggered = false;
            for (int i = 0; i < 50; i++)
            {
                //Detect the CancellationToken
                if (!isCancellationHadBeenTriggered && cancellationToken.IsCancellationRequested)
                {
                    logger.Info("Signaled cancellation!");
                    isCancellationHadBeenTriggered = true;
                    //break;  //stop the loop right away
                    //or do the cancellation callback
                }

                logger.Info(String.Format("QBW Test-{0}", i));
                Thread.Sleep(2000);
            }
        }

        private async Task longRunningWriteLogAsync(
CancellationToken cancellationToken)
        {
            this.logRunningWriteLog(cancellationToken);
        }
    }


主程式(黃色底)的部分可簡化成
Func<CancellationToken, Task> workItem = async (cancellationToken) => { … };




重新發行到IIS後,一樣在第六次logging時回收集區,可以看到工作仍持續了約三十秒才停止。
符合文章最上面所提及的限制。



結論

QBWI在整體架構設計上,可能不是最完美的作法(實際上搭配MQ比較保險)
小弟之前在一些線上交易的專案中,為了Performance issue 會在Http response後,發另一個thread做通知及紀錄; 倒是可以應用在這些輕量動作上。

Let’s Fire and Forget on ASP.NET! (From Stephen Cleary)


Reference


l   hangfire

沒有留言:

張貼留言