2012-10-09 5 views
34

.NET 4.5でasync/awaitパターンを使用して、WCFでいくつかのサービスメソッドを実装しています。 例サービス:OperationContext.Currentは、WCFサービスでasync/awaitを初めて使用するのを待ってからnullです。

契約:

[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.Currentnullを返し、私はOperationContext.Current.IncomingMessageHeadersにアクセスすることができないということです。

awaitの前にコンテキストを取得できるので、この単純な例では問題はありません。しかし実際のケースでは、OperationContext.Currentは呼び出しスタックの深いところからアクセスされています。コンテキストをさらに渡すためには、たくさんのコードを変更する必要はありません。

awaitポイントの後に手動でスタックを通過させずに操作コンテキストを取得する方法はありますか?

+0

'Task'インスタンスをワイヤでクライアントにシリアル化することは何を意味しますか? – Steven

+0

async/awaitを使用すると、タスクはクライアントに渡されません。 Wcfはvoidを返すメソッドとしてそれを理解しています。そのようなサービスへの参照を追加するクライアントは、void DoSomething()を参照します。 – mdonatas

+1

それは面白いです。それでも、実際にこのような操作を実行したいとは思っていません。何らかの理由で操作が失敗した場合はどうしますか?クライアントはそれが成功裏に成功したと考えます。これらの操作をある種のトランザクションキューに入れておく方がよいでしょう。 – Steven

答えて

21

あなたの最良の選択肢は、実際にそれをキャプチャして手動で渡すことです。これにより、コードのテスト容易性が向上することがあります。

  1. それはLogicalCallContextに追加する:言っ

    は、他のオプションがいくつかあります。
  2. 自分でSynchronizationContextをインストールします。OperationContext.CurrentにはPostが設定されます。これはASP.NETがそのHttpContext.Currentを保存する方法です。
  3. OperationContext.Currentと設定して独自のTaskSchedulerをインストールしてください。

また、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、この質問の主題へのアプローチを決定しようとしていますが、ctxを直接使用するのではなく、上記のコードでOperationContextScopeを使用する理由を説明できるかどうかは疑問でした。 (私はここに詳細を掲載しましたhttp://stackoverflow.com/questions/13290146/async-wcf-method-weboperationcontext-is-null-after-await) –

+2

OperationContextScopeを使用する主な理由は、OperationContext.Currentを1つはコンストラクタに渡されました。これにより、ctxインスタンスを深い呼び出しスタックに渡す必要がなくなります(たとえば、OperationContextパラメータを取得するためにメソッドのシグネチャを変更する必要がないなど)。 – JonCole

2

幸いにも私たちの現実的なサービス実装は、Unity IoCコンテナによってインスタンス化されます。これにより、に設定されたIWcfOperationContextを作成することができました。これは、RealServiceの各インスタンスに対してWcfOperationContextというインスタンスが1つだけ存在することを意味します。
WcfOperationContextのコンストラクタでは、OperationContext.Currentをキャプチャし、それを必要とするすべての場所でIWcfOperationContextを取得します。これはStephen Clearyが彼の答えで示唆したものである。

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); 
    } 
} 

と使用方法:

3

はここでサンプルSynchronizationContext実装だ氏クリアリーさん#1オプションを拡張し

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は置き換えられません。だから私は、SynchronizationContext.Currentがnullの場合は、SynchronizationContext.SetSynchronizationContext(this)を呼び出すと、チェックでオーバーライドされたPostメソッドを拡張する必要がありました。私はそれが正しい方法であるかどうかわからないが、それは私のために働いた。 –

3

、次のコードは、WCFサービスのコンストラクタにして配置することができます論理呼び出しコンテキスト内のOperationContextを格納して取得します。

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

は、どこでもあなたは、次のようなものを書くことができ、ヌルコンテキストの問題を抱えている:

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

免責事項:これは歳のコードであると私は私がでelse ifが必要な理由を覚えていませんコンストラクタですが、それは非同期と関係があり、私の場合はそれが必要であることが分かりました。

-2

更新:下記のコメントで指摘されているように、このソリューションはスレッドセーフではありませんので、上記の解決策は依然として最良の方法です。

私はDIコンテナ(Application_BeginRequest)にHttpContextを登録して、問題が発生したときにいつでも解決します。

登録:

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

解決:

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

これはスレッドセーフであるという疑問があります。基本的に同じ時間に2つのリクエスト(AとB)がある場合はどうなりますか? Aはこれを登録し、移動し、Bはこれを登録し、Aはインスタンスを解決します。そこにBを入れます。 – mdonatas

+0

@mdonatesに感謝します。確かに、それはあなたが望むもののような問題を引き起こす可能性があります。私は私の作図板に戻って何か他のものを取り上げるでしょう。乾杯。 – wind23

6

ネット4.6.2で修正されているようです。 the announcement

+0

私は4.6.2にアップし、私のOperationContextはまだ待ち状態のままです。私はこれもappSettingsを追加しないことがわかりましたが、何も変更されていません: Rhyous

+0

@万一 'configureAwait(false)'で待っています? – DixonD

+0

私のプロジェクトはユニークだと心配です。私のプロジェクトはEntity Anywhere Frameworkと呼ばれています。これは、ビジネスアプリケーションフレームワークであり、私はそれらのすべてに数十のシステムとエンティティを持ち、それらの上に構築したいと考えて設計されています。とにかく、エンティティごとにREST wcfサービスを動的に生成しています。自分のデザインを使ってデフォルトプロジェクトをテストする必要があります。 https://github.com/rhyous/EntityAnywhere – Rhyous

関連する問題