概述
IIS线程池中的线程数量是有限制的。当有多个长时间请求时,可能会耗尽IIS可用线程。出现503错误。在MVC中。当遇到非CPU操作的长时间请求时,MVC提供了异步方法来解决这个问题。
例:利用async和await实现异步方法
// GET: Async [AsyncTimeout(1000)] public async Task<ActionResult> Index() { var data = await GetPageTaskAsync("http://www.baidu.com"); return data; }
回到Orchard,在Orchard启动时,需要一定时间加载模块插件,这时候如果出现大量请求,则有可能出现上面提到的错误。来看一下Orchard中是如何解决这个问题。
项目结构Orchard.WarmupStarter ,是一个单独的项目。方便复用
具体实现
一句话概括实现步骤: 启动时初始化一个异步请求列表,初始化期间有请求到来时,把该请求添加到请求列表中。当系统初始化完成时回调起步请求列表并且清空。
Starter.cs
/// <summary> /// Run the initialization delegate asynchronously in a queued work item /// </summary> public void LaunchStartupThread(HttpApplication application) { // Make sure incoming requests are queued WarmupHttpModule.SignalWarmupStart(); ThreadPool.QueueUserWorkItem( state => { try { var result = _initialization(application); _initializationResult = result; } catch (Exception e) { lock (_synLock) { _error = e; _previousError = null; } } finally { // Execute pending requests as the initialization is over WarmupHttpModule.SignalWarmupDone(); } }); } WarmupHttpModule.SignalWarmupStart(); 系统开始加载,初始化请求列表 public static void SignalWarmupStart() { lock (_synLock) { if (_awaiting == null) { _awaiting = new List<Action>(); } } } WarmupHttpModule.SignalWarmupDone(); 系统加载完成,回调请求列表并且清空 public static void SignalWarmupDone() { IList<Action> temp; lock (_synLock) { temp = _awaiting; _awaiting = null; } if (temp != null) { foreach (var action in temp) { action(); } } }
WebConfig中注册WarmupHttpModule
请求到来时,执行WarmupHttpModule.BeginBeginRequest回调, 如果加载中,则请求添加到异步列表,否则继续执行回调
private IAsyncResult BeginBeginRequest(object sender, EventArgs e, AsyncCallback cb, object extradata) { // host is available, process every requests, or file is processed if (!InWarmup() || WarmupUtility.DoBeginRequest(_context)) { var asyncResult = new DoneAsyncResult(extradata); cb(asyncResult); return asyncResult; } else { // this is the "on hold" execution path var asyncResult = new WarmupAsyncResult(cb, extradata); Await(asyncResult.Completed); return asyncResult; } }
异步编程模型 DoneAsyncResult 和 WarmupAsyncResult 实现IAsyncResult
WarmupAsyncResult
/// <summary> /// AsyncResult for "on hold" request (resumes when "Completed()" is called) /// </summary> private class WarmupAsyncResult : IAsyncResult { private readonly EventWaitHandle _eventWaitHandle = new AutoResetEvent(false/*initialState*/); private readonly AsyncCallback _cb; private readonly object _asyncState; private bool _isCompleted; public WarmupAsyncResult(AsyncCallback cb, object asyncState) { _cb = cb; _asyncState = asyncState; _isCompleted = false; } public void Completed() { _isCompleted = true; _eventWaitHandle.Set(); _cb(this); } bool IAsyncResult.CompletedSynchronously { get { return false; } } bool IAsyncResult.IsCompleted { get { return _isCompleted; } } object IAsyncResult.AsyncState { get { return _asyncState; } } WaitHandle IAsyncResult.AsyncWaitHandle { get { return _eventWaitHandle; } } }
DoneAsyncResult(不阻塞)
/// <summary> /// Async result for "ok to process now" requests /// </summary> private class DoneAsyncResult : IAsyncResult { private readonly object _asyncState; private static readonly WaitHandle _waitHandle = new ManualResetEvent(true/*initialState*/); public DoneAsyncResult(object asyncState) { _asyncState = asyncState; } bool IAsyncResult.CompletedSynchronously { get { return true; } } bool IAsyncResult.IsCompleted { get { return true; } } WaitHandle IAsyncResult.AsyncWaitHandle { get { return _waitHandle; } } object IAsyncResult.AsyncState { get { return _asyncState; } } }
总结
Orchard.WarmupStarter 已封装好相关热启动代码, 实际项目中如果初始化时间比较长,稍改造Orchard.WarmupStarter就可复用到自己的项目中。
参考
http://www.cnblogs.com/alby/archive/2012/10/18/orchard-WarmupStarter.html