とCorrelationManager.LogicalOperationStack互換性があります。が背景情報については、この質問を参照してくださいParallel.For、タスク、スレッドなど
How do Tasks in the Task Parallel Library affect ActivityID?
をその質問タスクTrace.CorrelationManager.ActivityIdにどのように影響するかを尋ねます。 @Greg Samsonは、ActivityIdがTasksのコンテキストで信頼できることを示すテストプログラムで自分の質問に答えました。テストプログラムは、タスクデリゲートの先頭にActivityIdを設定し、スリープして作業をシミュレートし、最後にActivityIdをチェックして、同じ値(別のスレッドによって変更されていない)であることを確認します。プログラムは正常に実行されます。
スレッディング、タスク、およびパラレル操作(最終的にはより良いロギングのコンテキストを提供する)のための他の "コンテキスト"オプションを調べているうちに、Trace.CorrelationManager.LogicalOperationStackという奇妙な問題が発生しました。私は下の彼の質問に私の "答え"をコピーしました。
私は、Trace.CorrelationManager.LogicalOperationStackが壊れているように見えます(Parallel.Forのコンテキストで使用される場合は、Parallel.For自体が論理演算)。
ここに私の質問は以下のとおりです。
がParallel.Forで使用可能Trace.CorrelationManager.LogicalOperationStackべきでしょうか?そうであれば、Parallel.Forで論理演算が既に有効になっている場合に変更を加える必要がありますか?
Parallel.ForでLogicalOperationStackを使用する「正しい」方法はありますか?私はこのサンプルプログラムを別の方法で "動作する"ようにコーディングできますか? 「作品」とは、LogicalOperationStackが常に予想されるエントリ数を持ち、エントリ自体が予想されるエントリであることを意味します。
私はスレッドとThreadPoolのスレッドを使用して、いくつかの追加のテストを行っているが、私は戻って、私は同様の問題に遭遇したかどうかを確認するためにこれらのテストを再試行する必要があります。
Task/ParallelスレッドとThreadPoolスレッドは、親スレッドからTrace.CorrelationManager.ActivityIdとTrace.CorrelationManager.LogicalOperationStack値を「継承する」と表示されます。これは、これらの値がCallContextのLogicalSetDataメソッド(SetDataではなく)を使用してCorrelationManagerによって格納されるために必要です。
繰り返しますが、私は以下の投稿「答え」のために元のコンテキストを取得するために戻って、この質問を参照してください。
How do Tasks in the Task Parallel Library affect ActivityID?
も(これまでに回答されていません)この同様の質問を参照してくださいMicrosoftのパラレル拡張機能のフォーラムで:
01 [PASTEをBEGIN]
CorrelationManagerの動作とスレッド/タスク/などを扱うので、あなたの質問には本当に答えることはできませんが、回答として私の投稿をこれを許してください。私はCorrelationManagerのLogicalOperationStack
(およびStartLogicalOperation/StopLogicalOperation
メソッド)を使用して、マルチスレッドシナリオで追加のコンテキストを提供する方法を検討してきました。
Parallel.Forを使用して作業を並行して実行する機能を追加するために、私はあなたの例をとり、わずかに変更しました。また、StartLogicalOperation/StopLogicalOperation
を使用して(内部で)DoLongRunningWork
を括弧で括っています。概念的には、DoLongRunningWork
は、それが実行されるたびに、このような何かを行います。
DoLongRunningWork
StartLogicalOperation
Thread.Sleep(3000)
StopLogicalOperation
を私は(あるとして多かれ少なかれ)あなたのコードにこれらの論理演算を追加した場合、論理operatinsのすべてが(同期に残ることを私が発見しました常にスタック上で期待されるオペレーションの数とスタック上のオペレーションの値は常に期待通りです)。
私自身のテストでは、これは必ずしもそうではないことがわかりました。論理演算スタックが壊れていました。私が思いつくことができる最も良い説明は、 "子"スレッドが終了したときにCallContext情報を "親"スレッドコンテキストに戻す "合併"が、 "古い"子スレッドコンテキスト情報(論理演算)を "別の兄弟の子スレッドによって継承されます。
この問題は、Parallel.Forが明らかに "ワーカースレッド"の1つとしてメインスレッド(少なくともサンプルコードでは書かれているように)を使用しているという事実に関連している可能性がありますパラレルドメイン)。 DoLongRunningWorkが実行されるたびに、新しい論理操作が(開始時に)開始され、終了時に停止されます(つまり、LogicalOperationStackにプッシュされ、それからポップされます)。メインスレッドにすでに有効な論理演算があり、DoLongRunningWorkがMAIN THREADで実行すると、新しい論理演算が開始され、メインスレッドのLogicalOperationStackはTWO演算を持つようになります。 DoLongRunningWorkの後続の実行(DoLongRunningWorkのこの "反復"がメインスレッド上で実行されている限り)は、(明らかに)メインスレッドのLogicalOperationStackを継承します(LogicalOperationStackには期待される操作ではなく2つの操作があります)。
私の例でLogicalOperationStackの動作が私の変更されたバージョンのものと異なるのはなぜか分かりませんでした。最後に私のコードでは、プログラム全体を論理演算で囲んだのに対し、テストプログラムの修正版ではそうではありませんでした。私のテストプログラムでは、「仕事」が実行されるたびに(DoLongRunningWorkに類似している)、すでに論理的な操作が有効であったということです。あなたのテストプログラムの私の修正版では、私は論理的な操作でプログラム全体を括弧で囲まなかった。
私は、Parallel.Forを使用している場合、プログラム全体を論理演算で囲むようにテストプログラムを変更しましたが、まったく同じ問題が発生しました。上記の概念モデルを使用して
が、これは正常に実行されます:
Parallel.For
DoLongRunningWork
StartLogicalOperation
Sleep(3000)
StopLogicalOperation
これは最終的に明らかに同期LogicalOperationStackの外に起因して主張しますが:ここ
StartLogicalOperation
Parallel.For
DoLongRunningWork
StartLogicalOperation
Sleep(3000)
StopLogicalOperation
StopLogicalOperation
は私のサンプルプログラムです。 ActivityIdとLogicalOperationStackを操作するDoLongRunningWorkメソッドを持っている点で、あなたと似ています。私はDoLongRunningWorkの2つの味があります。 1つのフレーバは、Parallel.Forを使用するタスクを使用します。並列処理全体が論理演算で囲まれるように各フレーバを実行することもできます。したがって、並列操作を実行するための合計4つの方法があります。それぞれを試すには、目的の "使用..."メソッドのコメントを外し、再コンパイルして実行します。UseTasks
,UseTasks(true)
、およびUseParallelFor
はすべて完了まで実行する必要があります。 UseParallelFor(true)
は、LogicalOperationStackに予想されるエントリ数がないため、ある時点でアサートします。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
namespace CorrelationManagerParallelTest
{
class Program
{
static void Main(string[] args)
{
//UseParallelFor(true) will assert because LogicalOperationStack will not have expected
//number of entries, all others will run to completion.
UseTasks(); //Equivalent to original test program with only the parallelized
//operation bracketed in logical operation.
////UseTasks(true); //Bracket entire UseTasks method in logical operation
////UseParallelFor(); //Equivalent to original test program, but use Parallel.For
//rather than Tasks. Bracket only the parallelized
//operation in logical operation.
////UseParallelFor(true); //Bracket entire UseParallelFor method in logical operation
}
private static List<int> threadIds = new List<int>();
private static object locker = new object();
private static int mainThreadId = Thread.CurrentThread.ManagedThreadId;
private static int mainThreadUsedInDelegate = 0;
// baseCount is the expected number of entries in the LogicalOperationStack
// at the time that DoLongRunningWork starts. If the entire operation is bracketed
// externally by Start/StopLogicalOperation, then baseCount will be 1. Otherwise,
// it will be 0.
private static void DoLongRunningWork(int baseCount)
{
lock (locker)
{
//Keep a record of the managed thread used.
if (!threadIds.Contains(Thread.CurrentThread.ManagedThreadId))
threadIds.Add(Thread.CurrentThread.ManagedThreadId);
if (Thread.CurrentThread.ManagedThreadId == mainThreadId)
{
mainThreadUsedInDelegate++;
}
}
Guid lo1 = Guid.NewGuid();
Trace.CorrelationManager.StartLogicalOperation(lo1);
Guid g1 = Guid.NewGuid();
Trace.CorrelationManager.ActivityId = g1;
Thread.Sleep(3000);
Guid g2 = Trace.CorrelationManager.ActivityId;
Debug.Assert(g1.Equals(g2));
//This assert, LogicalOperation.Count, will eventually fail if there is a logical operation
//in effect when the Parallel.For operation was started.
Debug.Assert(Trace.CorrelationManager.LogicalOperationStack.Count == baseCount + 1, string.Format("MainThread = {0}, Thread = {1}, Count = {2}, ExpectedCount = {3}", mainThreadId, Thread.CurrentThread.ManagedThreadId, Trace.CorrelationManager.LogicalOperationStack.Count, baseCount + 1));
Debug.Assert(Trace.CorrelationManager.LogicalOperationStack.Peek().Equals(lo1), string.Format("MainThread = {0}, Thread = {1}, Count = {2}, ExpectedCount = {3}", mainThreadId, Thread.CurrentThread.ManagedThreadId, Trace.CorrelationManager.LogicalOperationStack.Peek(), lo1));
Trace.CorrelationManager.StopLogicalOperation();
}
private static void UseTasks(bool encloseInLogicalOperation = false)
{
int totalThreads = 100;
TaskCreationOptions taskCreationOpt = TaskCreationOptions.None;
Task task = null;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StartLogicalOperation();
}
Task[] allTasks = new Task[totalThreads];
for (int i = 0; i < totalThreads; i++)
{
task = Task.Factory.StartNew(() =>
{
DoLongRunningWork(encloseInLogicalOperation ? 1 : 0);
}, taskCreationOpt);
allTasks[i] = task;
}
Task.WaitAll(allTasks);
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StopLogicalOperation();
}
stopwatch.Stop();
Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds));
Console.WriteLine(String.Format("Used {0} threads", threadIds.Count));
Console.WriteLine(String.Format("Main thread used in delegate {0} times", mainThreadUsedInDelegate));
Console.ReadKey();
}
private static void UseParallelFor(bool encloseInLogicalOperation = false)
{
int totalThreads = 100;
Stopwatch stopwatch = new Stopwatch();
stopwatch.Start();
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StartLogicalOperation();
}
Parallel.For(0, totalThreads, i =>
{
DoLongRunningWork(encloseInLogicalOperation ? 1 : 0);
});
if (encloseInLogicalOperation)
{
Trace.CorrelationManager.StopLogicalOperation();
}
stopwatch.Stop();
Console.WriteLine(String.Format("Completed {0} tasks in {1} milliseconds", totalThreads, stopwatch.ElapsedMilliseconds));
Console.WriteLine(String.Format("Used {0} threads", threadIds.Count));
Console.WriteLine(String.Format("Main thread used in delegate {0} times", mainThreadUsedInDelegate));
Console.ReadKey();
}
}
}
この全体のLogicalOperationStackはParallel.Forで使用することができる場合の問題(および/または他のスレッド/タスクの構築)、またはどのようにそれがメリットに、おそらく、独自の質問を使用することができます。たぶん私は質問を投稿します。その間、私はあなたにこれについての考えがあるかどうか疑問に思います(または、ActivityIdが安全であると思われるのでLogicalOperationStackの使用を検討していたのだろうか)。
[ENDのPASTE]
誰もがこの問題上の任意の考えを持っていますか?
LogicalOperationStackとParallel.Forに関するご意見やご提案はありますか? – wageoghe
犯人を探すのを救済してくれた徹底的な調査に感謝します:) –