那里有很多有用的.NET API(例如Microsoft Entity Framework的API DbContext),它們提供了旨在與之一起使用的異步方法await,但據記錄它們不是線程安全的。這使其非常適合在桌面UI應用程序中使用,但不適用于服務器端應用程序。[編輯] 這可能實際上并不適用DbContext,這是Microsoft 關于EF6線程安全性的聲明,請自行判斷。 [/編輯]也有一些已建立的代碼模式屬于同一類別,例如使用OperationContextScope(在此處和此處詢問)調用WCF服務代理,例如:using (var docClient = CreateDocumentServiceClient())using (new OperationContextScope(docClient.InnerChannel)){ return await docClient.GetDocumentAsync(docId);}這可能會失敗,因為OperationContextScope在其實現中使用線程本地存儲。問題的根源AspNetSynchronizationContext是在異步ASP.NET頁中使用它來以更少的線程ASP.NET池線程來滿足更多的HTTP請求。使用AspNetSynchronizationContext,await可以在與發起異步操作的線程不同的線程上排隊一個延續,同時將原始線程釋放到池中,并可以用于服務另一個HTTP請求。這大大提高了服務器端代碼的可伸縮性。該機制在必須閱讀的“關于同步上下文的全部內容”中進行了詳細描述。因此,盡管不涉及并發API訪問,但是潛在的線程切換仍然阻止我們使用上述API。我一直在考慮如何在不犧牲可伸縮性的情況下解決此問題。顯然,收回這些API的唯一方法是針對可能受到線程切換影響的異步調用范圍保持線程親和性。假設我們具有這種線程相似性。無論如何,這些調用中的大多數都是受IO約束的(沒有線程)。當異步任務掛起時,它所源自的線程可用于服務另一個類似任務的延續,該結果已經可用。因此,它不應過多地損害可伸縮性。這種方法并不是什么新鮮事物,實際上,Node.js已成功使用了類似的單線程模型。IMO,這是使Node.js如此受歡迎的原因之一。我不明白為什么這種方法不能在ASP.NET上下文中使用。定制任務調度程序(我們稱之為ThreadAffinityTaskScheduler)可以維護一個單獨的“親和公寓”線程池,以進一步提高可伸縮性。一旦任務已排隊到那些“公寓”線程之一中,await任務內部的所有繼續將在同一線程上進行。這是鏈接問題中的非線程安全API 可以與此類方法一起使用的方式ThreadAffinityTaskScheduler:// create a global instance of ThreadAffinityTaskScheduler - per web apppublic static class GlobalState { public static ThreadAffinityTaskScheduler TaScheduler { get; private set; } public static GlobalState { GlobalState.TaScheduler = new ThreadAffinityTaskScheduler( numberOfThreads: 10); }}// ...基于Stephen Toub的出色表現,我繼續進行并作為概念證明進行了實施ThreadAffinityTaskSchedulerStaTaskScheduler。ThreadAffinityTaskScheduler在經典的COM意義上,由其維護的池線程不是STA線程,但是它們確實實現了線程的await連續性(SingleThreadSynchronizationContext對此負責)。到目前為止,我已經將該代碼作為控制臺應用程序進行了測試,并且看起來可以按設計工作。我尚未在ASP.NET頁面中對其進行測試。我沒有大量的ASP.NET生產開發經驗,所以我的問題是:在ASP.NET中非線程安全的API的簡單同步調用上使用這種方法是否有意義(主要目標是避免犧牲可伸縮性)?除了使用同步API調用或完全避免這些APis之外,還有其他方法嗎?有沒有人在ASP.NET MVC或Web API項目中使用過類似的東西,并準備分享他/她的經驗?任何有關如何使用ASP.NET進行壓力測試和概要分析的建議都將受到贊賞。
如何在ASP.NET Web API中使用非線程安全的異步/等待API和模式?
寶慕林4294392
2019-12-12 14:09:53