2012-03-20 23 views
13

ノーマル/同期/シングルスレッドコンソールアプリケーションでは、NDC.Pushは「現在のアイテム」(潜在的に複数のネストレベルで、この例では1レベル)を管理するために正常に動作します。例えばasync/awaitメソッドを使用してNDCのようなlog4netスタックを管理する方法は?

private static ILog s_logger = LogManager.GetLogger("Program"); 

static void Main(string[] args) 
{ 
    BasicConfigurator.Configure(); 

    DoSomeWork("chunk 1"); 
    DoSomeWork("chunk 2"); 
    DoSomeWork("chunk 3"); 
} 

static void DoSomeWork(string chunkName) 
{ 
    using (NDC.Push(chunkName)) 
    { 
     s_logger.Info("Starting to do work"); 
     Thread.Sleep(5000); 
     s_logger.Info("Finishing work"); 
    } 
} 

これは期待するだけの「プログラム」(基本コンフィギュレータのデフォルトパターン)

の右にある「チャンクX」NDCエントリを示し、出力をログになります

232 [9] INFOプログラムチャンク1 - 仕事を行うために開始し

5279 [9] INFOプログラムチャンク1 - 仕上げ作業

5279 [9] INFOプログラムチャンク2 - 仕事を行うために開始し

10292 [9] INFOプログラムチャンク2 - 仕上げ作業

10292 [9] INFOプログラムのチャンク3 - 仕事に

を行うために開始15299 [9] INFOプログラムチャンク3 - 仕上げ作業

しかし、私は「通常の」非同期メソッドを使用してそれを維持する方法を理解できません。このやろうとして例えば

、:

private static ILog s_logger = LogManager.GetLogger("Program"); 

static void Main(string[] args) 
{ 
    BasicConfigurator.Configure(); 

    var task1 = DoSomeWork("chunk 1"); 
    var task2 = DoSomeWork("chunk 2"); 
    var task3 = DoSomeWork("chunk 3"); 

    Task.WaitAll(task1, task2, task3); 
} 

static async Task DoSomeWork(string chunkName) 
{ 
    using (log4net.LogicalThreadContext.Stacks["NDC"].Push(chunkName)) 
    //using (log4net.ThreadContext.Stacks["NDC"].Push(chunkName)) 
    { 
     s_logger.Info("Starting to do work"); 
     await Task.Delay(5000); 
     s_logger.Info("Finishing work"); 
    } 
} 

はすべて「正常」開始、それらを表示しますが、タスクは別のスレッドで完了すると、スタックが失われているが(私はlog4net.LogicalThreadContextを望んでいました。私が推測するように、TPL-'aware 'とする)。

234 [10] INFOプログラムチャンク1 - 仕事をするために開始

265 [10] INFOプログラムチャンク2 - 仕事をするために開始

265 [10] INFOプログラムチャンク3 - 開始仕事を行うために

5280 [7] INFOプログラム(ヌル) - 仕上げ作業

5280 [12] INFOプログラム(ヌル) - 仕上げ作業

5280 [12] INFOプログラム(ヌル) - log4netの新しいTaskContext(など)を追加することの外で作業

を仕上げ、この種の活動を追跡する方法はありますか?

実際には、async/await構文の砂糖を使用するのが本当に目的です。スレッドアフィニティを強制するか、タスクごとに並行して辞書を保持するなどの作業を実行可能なオプションにする可能性が高いですが、できるだけ同期バージョンのコードに近いものを使用してください。 :)

+5

私は最近、Microsoftが 'async'を扱うために.NET 4.5 RTWで' CallContext'を修正したことを発見しました。 log4netのNDCや 'Logical * Data'を使った他のソリューションは' async'メソッド(.NET 4.5のみ)で期待通りに動作します。 –

+0

@StephenCleary素晴らしい!ありがとう! –

+0

私はそれが論理スレッドのコンテキストから来ている問題であるかどうかはわかりません。親と子のスレッドが同じスタックを共有しているので、log4netの実装は間違っています。子が親スタックのクローンを受け取るようにして、親がスタックを変更しても子を混乱させないようにしてください... –

答えて

15

現在、async論理呼び出しコンテキストの良い話はありません。

CallContextは使用できません。論理 CallContextasyncメソッドが早期に戻り、後で再開する方法を理解していないため、 Task.WhenAllなどの単純な並列処理を使用するコードでは常に正しく動作するとは限りません。

更新:CallContextが正しくasync方法で動作するように、.NET 4.5 RTWに更新されました。

私はlog4netを調べました。 LogicalThreadContextCallContextを使用していると文書化されていますが、非論理コンテキストを使用するバグがありました(2012年2月2日のSVNで修正されましたが、現在の1.2.11リリースには修正が含まれていません)。しかし、それが修正されても、論理CallContextasyncでは機能しないため、asyncではまだ動作しません。

async論理呼び出しコンテキストが必要な場合は、コンテキストデータを含むクラスを作成し、すべてのasyncメソッドをそのクラスのインスタンスメンバーとして機能スタイルに保ちます。これはです。確かにという理想的な解決策ではありませんが、それは機能する汚いハックです。

一方、suggestion that Microsoft provide some mechanism for thisをアップしてください。

P.S. メソッドは必ずしも実行中のタスク(つまり、usingステートメントのTask.CurrentIdは実際にその時点で実行されているタスクがないためnullとなるため)を実行しているわけではないため、Taskのキーワードを使用する同時辞書は機能しません。

スレッドアフィニティには独自の問題もあります。実際には、独立した非同期操作ごとに個別のスレッドが必要になります。 Bye-bye、スケーラビリティ...

+0

申し訳ありませんが、並行辞書アプローチでは非同期メソッドを使用しませんタスクを使って「明示的」になる(つまり、VS2010/.NET4で何ができるのか)。私は本当にそれを考えていないが、私はちょうど(サブクラス化せずに自分自身で)タスクにデータを添付し、HttpContext.Currentに似た「現在のタスクコンテキスト」を得ることができます。 :) –

+0

私は参照してください。もしあなたが 'AsyncState'以外の' Task'にデータを添付したいのであれば、[Connected Properties](http://connectedproperties.codeplex.com/)を使うことができますが、IMOは 'このシナリオでは「ConcurrentDictionary」です。 –

+0

@StephenCleary Stephen Toubは、User Voiceのリンク先に、初期情報が古く、CallContextがasync/awaitに適していると答えました。 – Lex

関連する問題