2012-10-09 17 views
34

我使用.NET 4.5中的異步/等待模式來實現WCF中的某些服務方法。 示例服務:在WCF服務中使用異步/等待時第一次等待時,OperationContext.Current爲空

合同:

[ServiceContract(Namespace = "http://async.test/")] 
public interface IAsyncTest 
{ 
    Task DoSomethingAsync(); 
} 

實現:

MyAsyncService : IAsyncTest 
{ 
    public async Task DoSomethingAsync() 
    { 
     var context = OperationContext.Current; // context is present 

     await Task.Delay(10); 

     context = OperationContext.Current; // context is null 
    } 
} 

我遇到的問題是,第一awaitOperationContext.Current回報null後,我無法訪問OperationContext.Current.IncomingMessageHeaders

在這個簡單的例子中,這不是一個問題,因爲我可以在await之前捕獲上下文。但是在現實世界中,OperationContext.Current正從調用堆棧的內部進行訪問,我真的不想爲了進一步傳遞上下文而更改大量代碼。

有沒有辦法在await點之後獲得操作上下文,而無需手動將其傳遞到堆棧上?

+0

這是什麼意思序列化「任務」實例通過電線到客戶端? – Steven

+0

使用異步/等待時,任務不會傳遞給客戶端。 Wcf理解爲無效返回方法。添加對此類服務的引用的客戶端將看到void DoSomething(); – mdonatas

+1

這很有趣。不過,我不確定你是否真的想用這種方式來執行操作。由於某種原因,操作失敗時你會怎麼做?客戶認爲它成功成功。您最好將這些操作排列在某種事務隊列中。 – Steven

回答

21

我認爲你最好的選擇是實際捕獲它並手動傳遞它。您可能會發現這會提高代碼的可測試性。

這就是說,有幾個其他選項:

  1. 將它添加到LogicalCallContext
  2. 安裝您自己的SynchronizationContext,它會在設置OperationContext.Current時設置Post;這是ASP.NET如何保留其HttpContext.Current
  3. 安裝您自己的TaskScheduler其中OperationContext.Current

您可能還想在Microsoft Connect上提出此問題。

+1

+1捕獲+通過可測試性:) –

27

不幸的是,這不起作用,我們將看到在未來的版本中修復。

與此同時,有重新應用上下文當前線程,這樣你就不必繞過物體的方式:

public async Task<double> Add(double n1, double n2) 
    { 

     OperationContext ctx = OperationContext.Current; 

     await Task.Delay(100); 

     using (new OperationContextScope(ctx)) 
     { 
      DoSomethingElse(); 
     } 
     return n1 + n2; 
    } 

在上面的例子中,DoSomethingElse( )方法將按預期方式訪問OperationContext.Current。

+5

喬恩 - 你清楚地知道比大多數人的代表16(代表MSDN上找到你的博客)。我可以建議你更新你的個人資料,以便人們瞭解你的答案的質量。 – ErnieL

+0

Jon,我們只是試圖爲這個問題的主題做出決定,我想知道是否可以解釋爲什麼在上面的代碼中使用OperationContextScope,而不是直接使用ctx? (我在這裏發佈了更多細節http://stackoverflow.com/questions/13290146/async-wcf-method-weboperationcontext-is-null-after-await) –

+2

使用OperationContextScope的主要原因是將OperationContext.Current設置爲傳遞給構造函數的那個​​。這可以防止您必須將ctx實例傳遞給深層調用堆棧(例如,您不必修改方法簽名以獲取OperationContext參數)。 – JonCole

2

幸運的是,我們的實際服務實現通過Unity IoC容器實例化。這使我們能夠創建一個IWcfOperationContext,它被配置爲具有PerResolveLifetimeManager,這僅僅意味着對於RealService的每個實例,將只有一個WcfOperationContext實例。
WcfOperationContext的構造函數中,我們捕獲OperationContext.Current,然後所有需要它的地方從IWcfOperationContext得到它。這實際上是斯蒂芬克利裏在他的回答中提出的。

3

這裏是一個樣本SynchronizationContext實現:

public class OperationContextSynchronizationContext : SynchronizationContext 
{ 
    private readonly OperationContext context; 

    public OperationContextSynchronizationContext(IClientChannel channel) : this(new OperationContext(channel)) { } 

    public OperationContextSynchronizationContext(OperationContext context) 
    { 
     OperationContext.Current = context; 
     this.context = context; 
    } 

    public override void Post(SendOrPostCallback d, object state) 
    { 
     OperationContext.Current = context; 
     d(state); 
    } 
} 

與用法:

var currentSynchronizationContext = SynchronizationContext.Current; 
try 
{ 
    SynchronizationContext.SetSynchronizationContext(new OperationContextSynchronizationContext(client.InnerChannel)); 
    var response = await client.RequestAsync(); 
    // safe to use OperationContext.Current here 
} 
finally 
{ 
    SynchronizationContext.SetSynchronizationContext(currentSynchronizationContext); 
} 
+0

在等待調用的嵌套級別的情況下,當前SynchronizationContext不會被替換。所以我不得不用一個檢查來擴展重寫的Post方法,如果一個SynchronizationContext.Current爲空,我調用SynchronizationContext.SetSynchronizationContext(this)。我不知道這是否是正確的方式,但它對我有效。 –

3

擴展在克利先生的#1選項,下面的代碼可以被放置在WCF服務的構造函數在邏輯調用上下文中存儲和檢索OperationContext

if (CallContext.LogicalGetData("WcfOperationContext") == null) 
{ 
    CallContext.LogicalSetData("WcfOperationContext", OperationContext.Current); 
} 
else if (OperationContext.Current == null) 
{ 
    OperationContext.Current = (OperationContext)CallContext.LogicalGetData("WcfOperationContext"); 
} 

就這樣,在任何地方您有一個空的上下文的問題,你可以寫類似以下內容:

var cachedOperationContext = CallContext.LogicalGetData("WcfOperationContext") as OperationContext; 
var user = cachedOperationContext != null ? cachedOperationContext.ServiceSecurityContext.WindowsIdentity.Name : "No User Info Available"; 

聲明:本歲的代碼,我不記得我所需要的else if的原因構造函數,但它與async有關,我知道它是我需要的。

-2

更新:正如在下面的評論中指出的,這個解決方案不是線程安全的,所以我想上面討論的解決方案仍然是最好的方法。

我通過將HttpContext註冊到我的DI容器(Application_BeginRequest)中解決問題,並在需要時解決它。

註冊:

this.UnityContainer.RegisterInstance<HttpContextBase>(new HttpContextWrapper(HttpContext.Current)); 

解析:

var context = Dependencies.ResolveInstance<HttpContextBase>(); 
+0

我懷疑這是線程安全的。就像在基本同一時間有兩個請求(A和B)會發生什麼一樣。 A註冊這個,繼續前進,B註冊這個和A解決實例...實例放在那裏由B. – mdonatas

+0

感謝您的反饋,@ mdonates。事實上,它可能會造成像你所描述的問題。我將回到我的繪圖板並制定其他的東西。乾杯。 – wind23

6

這似乎是固定在.net 4.6.2。請參閱the announcement

+0

我解壓到4.6.2,並且我的OperationContext在等待調用中仍然爲空。我發現一個不會添加這個appSettings,但沒有任何改變: Rhyous

+0

@Rhyous你是否正在用'configureAwait(false)'等待任何機會? – DixonD

+0

我很擔心我的項目是獨一無二的。我的項目被稱爲Entity Anywhere Framework。這是一個商業應用程序框架,其設計思想是我將在其中擁有十幾個系統和實體,並且我希望在這些系統和實體之上構建。無論如何,我爲每個實體動態生成REST wcf服務。我需要使用我的設計測試默認項目。 https://github.com/rhyous/EntityAnywhere – Rhyous