Asp.Net異常Asynchronous operations are not allowed in this context的解決方案

來源:互聯網
上載者:User

    昨天碰見了一個怪事,同樣的一個方法A(),在控制台程式中調用和在Asp.Net頁面中調用,結果完全不一樣。在控制台程式中運行正常,在Asp.Net下不能得到正確結果。

 

    方法A()的執行過程如下:

 

    A()調用第三方庫的非同步方法呼叫B(),在非同步方法呼叫的回調方法C()之中,又調用了非同步WebService Request。
 
    經過調試發現,在C()中,吞噬了系統拋出的異常。修改代碼,捕獲到一個異常:

 

    Asynchronous operations are not allowed in this context. Page starting an asynchronous operation has to have the Async attribute set to true and an asynchronous operation can only be started on a page prior to PreRenderComplete event.

 

    搜尋 Google 發現,asp.net 對非同步呼叫進行了限制,如要在 Page 中進行非同步作業,需要設定Page的 Async = true; 然而,問題是在Page中觸發的僅僅是方法A,A再調用方法B,B的回調則是由無名的苦工線程在幕後執行的,C的回調又是一個無名的苦工線程在幕後執行的,兩個苦工線程,和Page線程根本不是一個線程,因此,在Page中設定Async=true能否影響到後台苦工線程值得懷疑。
 
    在Page中加入 Async =  true,果然,還是不能得出正確結果。無奈之下,猜測能否在設定檔中設定一項,啟用全站的非同步作業。搜尋設定項,無果,只好尋求第三條道路。
 
    仔細分析異常源,是由一個System.Web.AspNetSynchronizationContext執行個體的OperationStarted()方法拋出的,在MSDN中沒搜到這個類。動用.Net Reflector,原來是一個internal類,OperationStarted()方法如下:

 

 public override void OperationStarted()
 {
     if (this._invalidOperationEncountered || (this._disabled && (this._pendingCount == 0)))
     {
         this._invalidOperationEncountered = true;
         throw new InvalidOperationException(SR.GetString("Async_operation_disabled"));
     }
     Interlocked.Increment(ref this._pendingCount);
 }

 

    持有 AspNetSynchronizationContext 的是一個靜態類 System.ComponentModel.AsyncOperationManager的靜態屬性SynchronizationContext。
 
     用reflecter查看代碼, SynchronizationContext的getter擷取的是當前線程的SynchronizationContext,它的setter存取的是當前線程的SynchronizationContext。也就是說,對於拋出異常的線程,它的SynchronizationContext是一個AspNetSynchronizationContext執行個體。
 
    編寫測試程式發現,控制台程式的SynchronizationContext使用的SynchronizationContext類型是 System.Threading.SynchronizationContext,Asp.Net程式使用的SynchronizationContext類型是System.Web.AspNetSynchronizationContext。

 

    .Net 程式中,WebService使用SoapHttpClientProtocol進行Soap Request,而SoapHttpClientProtocol是WebClient類的子類,在WebClient類的許多非同步方法呼叫中,都調用了System.ComponentModel.AsyncOperationManage.SynchronizationContext.OperationStarted()方法. 如果碰上當前線程應用的是AspNetSynchronizationContext,而該AspNetSynchronizationContext又不允許非同步作業,就出現了異常。
 
    瞭解到這些,解決方案呼之欲出,就是在 global.asax 裡 加上 System.ComponentModel.AsyncOperationManager.SynchronizationContext=new System.Threading.SynchronizationContext();
 
    測試一下,啊哈~~~失敗!!
 
    Page中列印出來的SynchronizationContext還是AspNetSynchronizationContext。
 
    用  Reflector 尋找 AsyncOperationManager 的被引用情況,探索方法 System.Web.HttpApplication+ThreadContext.Enter(Boolean) : Void。方法體:

 

internal void Enter(bool setImpersonationContext)
 {
     this._savedContext = HttpContextWrapper.SwitchContext(this._context);
     if (setImpersonationContext)
     {
         this.SetImpersonationContext();
     }
     this._savedSynchronizationContext = AsyncOperationManager.SynchronizationContext;
     AsyncOperationManager.SynchronizationContext = this._context.SyncContext;
     Guid requestTraceIdentifier = this._context.WorkerRequest.RequestTraceIdentifier;
     if (!(requestTraceIdentifier == Guid.Empty))
     {
         CallContext.LogicalSetData("E2ETrace.ActivityID", requestTraceIdentifier);
     }
     this._context.ResetSqlDependencyCookie();
     this._savedPrincipal = Thread.CurrentPrincipal;
     Thread.CurrentPrincipal = this._context.User;
     this.SetRequestLevelCulture(this._context);
     if (this._context.CurrentThread == null)
     {
         this._setThread = true;
         this._context.CurrentThread = Thread.CurrentThread;
     }
 }

 

    原來SynchronizationContext在這裡被偷梁換柱了。吃飽了撐的。
 
    你不仁,我不義。你將它換過去,我就將它換過來。在頁面的Page_Load()裡面加上:
 
    System.ComponentModel.AsyncOperationManager.SynchronizationContext=new System.Threading.SynchronizationContext();

 

    測試通過。

 

    不過這樣做副作用還不小,比如SynchronizationContext經常會被HttpApplication替換成AspNetSynchronizationContext,在這時進行非同步作業就會出問題,再比如,使用System.Threading.SynchronizationContext而不是AspNetSynchronizationContext,會導致Asp.Net自身出現異常,昨天運行時就有一次HttpApplication拋了個異常。
 
    為減少副作用,那就在調用方法A()之前替換一下SynchronizationContext,方法執行完畢,再替換過來:
 

            System.Threading.SynchronizationContext context = System.ComponentModel.AsyncOperationManager.SynchronizationContext;
            try
            {
                System.ComponentModel.AsyncOperationManager.SynchronizationContext = new System.Threading.SynchronizationContext();
    A();
            }
            finally
            {
                System.ComponentModel.AsyncOperationManager.SynchronizationContext = context;
            }

 

    測試通過。至今天,還沒發現什麼問題。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.