Web 應用效能瓶頸多半在資料庫存取(佔總時間 90%+)。LINQ to SQL 預設不支援非同步查詢,但透過擴充方法封裝 ADO.NET 的 BeginExecuteReader/EndExecuteReader,即可輕鬆實作非同步資料存取,配合 ASP.NET 非同步頁面機制顯著提升吞吐量。
.NET 1.x → 2.0 → 3.5 演進中,「匿名方法」與「Lambda Expression」極大簡化了非同步程式設計模式 (APM) 的實作:
private class AsyncResult<T> : IAsyncResult
{
public AsyncResult(object asyncState)
{
AsyncState = asyncState;
IsCompleted = false;
AsyncWaitHandle = new ManualResetEvent(false);
}
public object AsyncState { get; private set; }
public WaitHandle AsyncWaitHandle { get; private set; }
public bool CompletedSynchronously => false;
public bool IsCompleted { get; private set; }
public T Result { get; set; }
public Exception Exception { get; set; }
public void Complete()
{
IsCompleted = true;
((ManualResetEvent)AsyncWaitHandle).Set();
}
}
public static IAsyncResult BeginExecuteQuery(
this DataContext dataContext,
IQueryable query,
bool withNoLock,
AsyncCallback callback,
object asyncState)
{
SqlCommand cmd = dataContext.GetCommand(query, withNoLock);
dataContext.OpenConnection();
var asyncResult = new AsyncResult<DbDataReader>(asyncState);
cmd.BeginExecuteReader(ar =>
{
try
{
asyncResult.Result = cmd.EndExecuteReader(ar);
}
catch (Exception ex)
{
asyncResult.Exception = ex;
}
finally
{
asyncResult.Complete();
callback?.Invoke(asyncResult);
}
}, null);
return asyncResult;
}
public static List<T> EndExecuteQuery<T>(
this DataContext dataContext,
IAsyncResult ar)
{
var asyncResult = (AsyncResult<DbDataReader>)ar;
if (!asyncResult.IsCompleted)
asyncResult.AsyncWaitHandle.WaitOne();
if (asyncResult.Exception != null)
throw asyncResult.Exception;
using (var reader = asyncResult.Result)
return dataContext.Translate<T>(reader).ToList();
}
public partial class AsyncPage : System.Web.UI.Page
{
private ItemDataContext _dataContext;
protected void Page_Load(object sender, EventArgs e)
{
AddOnPreRenderCompleteAsync(
new BeginEventHandler(BeginAsyncOperation),
new EndEventHandler(EndAsyncOperation));
}
private IAsyncResult BeginAsyncOperation(object sender, EventArgs e,
AsyncCallback cb, object state)
{
_dataContext = new ItemDataContext();
var query = (from item in _dataContext.Items
orderby item.ItemID
select item).Skip(10).Take(10);
return _dataContext.BeginExecuteQuery(query, cb, state);
}
private void EndAsyncOperation(IAsyncResult ar)
{
using (_dataContext.Connection)
{
rptItems.DataSource = _dataContext.EndExecuteQuery(ar);
rptItems.DataBind();
}
}
}
非同步資料存取屬資料存取層能力,若表現層(Page/Handler/Module)使用非同步,業務邏輯層 (BLL) 亦需提供對應的 Begin/End 配對。在多執行緒共享 BLL 實例時,需設計機制確保 End 訊息正確路由至對應的下層物件(例如使用狀態物件或 AsyncState 關聯),這將在後續文章探討。