Bitte verzeiht mein Posting dieses als Antwort, da es sich nicht wirklich um eine Antwort auf Ihre Frage handelt, sondern weil es sich um CorrelationManager-Verhalten und Threads/Tasks/etc handelt. Ich habe mir die LogicalOperationStack
(und StartLogicalOperation/StopLogicalOperation
) Methoden des CorrelationManager angesehen, um zusätzlichen Kontext in Multithreading-Szenarien bereitzustellen.
Ich nahm Ihr Beispiel und änderte es geringfügig, um die Fähigkeit zu ergänzen, Arbeit parallel mit Parallel.For durchzuführen. Außerdem verwende ich StartLogicalOperation/StopLogicalOperation
, um (intern) DoLongRunningWork
zu klammern. Konzeptionell tut DoLongRunningWork
so etwas wie dies jedes Mal ausgeführt wird:
DoLongRunningWork
StartLogicalOperation
Thread.Sleep(3000)
StopLogicalOperation
ich gefunden habe, dass, wenn ich diese logischen Operationen an Ihrem Code hinzufügen (mehr oder weniger wie), alle logischen operatins synchron bleiben (immer die erwartete Anzahl von Operationen auf Stack und die Werte der Operationen auf dem Stack sind immer wie erwartet).
In einigen meiner eigenen Tests fand ich, dass dies nicht immer der Fall war. Der Stapel logischer Operationen wurde "beschädigt". Die beste Erklärung, die ich finden könnte, ist, dass das "Zusammenführen" der CallContext-Informationen in den "Eltern" -Threadkontext, wenn der "Kind" -Thread beendet wird, die "alte" Kindthreadkontextinformation (logische Operation) verursacht hat. geerbt "von einem anderen Geschwister-Kind-Thread. Das Problem könnte auch damit zusammenhängen, dass Parallel.For anscheinend den Hauptthread (zumindest im Beispielcode, so wie er geschrieben wurde) als einen der "Worker Threads" (oder wie auch immer sie in der parallele Domäne). Immer wenn DoLongRunningWork ausgeführt wird, wird eine neue logische Operation gestartet (am Anfang) und gestoppt (am Ende) (dh auf den LogicalOperationStack geschoben und wieder entfernt). Wenn der Hauptthread bereits eine logische Operation ausführt und DoLongRunningWork über den Hauptthread ausgeführt wird, wird eine neue logische Operation gestartet, sodass der LogicalOperationStack des Hauptthreads nun ZWEI Operationen aufweist. Jede weitere Ausführung von DoLongRunningWork (solange diese "Iteration" von DoLongRunningWork im Hauptthread ausgeführt wird) wird (scheinbar) den LogicalOperationStack des Hauptthreads erben (der jetzt zwei Operationen hat und nicht nur die eine erwartete Operation).
Es hat lange gedauert, bis ich herausgefunden habe, warum das Verhalten von LogicalOperationStack in meinem Beispiel anders ist als in meiner modifizierten Version Ihres Beispiels. Schließlich sah ich, dass ich in meinem Code das gesamte Programm in eine logische Operation eingeklammert hatte, während ich das in meiner modifizierten Version Ihres Testprogramms nicht getan hatte. Die Implikation ist, dass in meinem Testprogramm jedes Mal, wenn meine "Arbeit" ausgeführt wurde (analog zu DoLongRunningWork), bereits eine logische Operation in Kraft war. In meiner modifizierten Version Ihres Testprogramms hatte ich das gesamte Programm nicht in einer logischen Operation eingeklammert.
Also, wenn ich Ihr Testprogramm geändert, um das gesamte Programm in einer logischen Operation und wenn ich Parallel.For Klammern, lief ich genau das gleiche Problem.
das konzeptionelle Modell oben verwenden, wird dies erfolgreich ausgeführt:
Parallel.For
DoLongRunningWork
StartLogicalOperation
Sleep(3000)
StopLogicalOperation
Während dies schließlich aufgrund eines durchsetzen wird offenbar nicht synchron Logicaloperation:
StartLogicalOperation
Parallel.For
DoLongRunningWork
StartLogicalOperation
Sleep(3000)
StopLogicalOperation
StopLogicalOperation
Hier ist mein Beispielprogramm ist. Es ähnelt Ihrer, indem es eine DoLongRunningWork-Methode enthält, die sowohl die ActivityId als auch den LogicalOperationStack manipuliert. Ich habe auch zwei Arten von Treten von DoLongRunningWork. Ein Flavor verwendet Tasks, die Parallel.For verwenden. Jede Geschmacksrichtung kann auch so ausgeführt werden, dass die gesamte parallelisierte Operation in einer logischen Operation eingeschlossen ist oder nicht. Es gibt also insgesamt 4 Möglichkeiten, den Parallelbetrieb auszuführen. Um die einzelnen zu testen, entfernen Sie einfach die gewünschte "Use ..." -Methode, kompilieren Sie sie und führen Sie sie aus. UseTasks
, UseTasks(true)
und UseParallelFor
sollten alle vollständig ausgeführt werden. UseParallelFor(true)
wird zu einem bestimmten Zeitpunkt aktiviert, da der LogicalOperationStack nicht die erwartete Anzahl von Einträgen hat.
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();
}
}
}
Dieses ganze Thema, wenn Logicaloperation mit Parallel.For verwendet werden (und/oder andere Threading/Aufgabe Konstrukte) oder wie kann es wohl verdient seine eigene Frage verwendet werden. Vielleicht werde ich eine Frage stellen. In der Zwischenzeit frage ich mich, ob Sie irgendwelche Gedanken dazu haben (oder ich frage mich, ob Sie LogicalOperationStack in Betracht gezogen haben, da ActivityId sicher zu sein scheint).
[EDIT]
Siehe meine Antwort auf this question für weitere Informationen über die Verwendung von Logicaloperation und/oder CallContext.LogicalSetData mit einigen der verschiedenen Thema/Thread/Aufgabe/Parallel contstructs.
Siehe auch hier meine Frage auf SO über Logicaloperation und parallele Erweiterungen: Is CorrelationManager.LogicalOperationStack compatible with Parallel.For, Tasks, Threads, etc
Schließlich sieht auch meine Frage hier auf Parallel Extensions Forum von Microsoft: http://social.msdn.microsoft.com/Forums/en-US/parallelextensions/thread/7c5c3051-133b-4814-9db0-fc0039b4f9d9
In meinen Tests es wie Trace aussieht. CorrelationManager.LogicalOperationStack kann bei Verwendung von Parallel.For oder Parallel.Invoke beschädigt werden, wenn Sie eine logische Operation im Hauptthread starten und dann logische Operationen im Delegate starten/stoppen. In meinen Tests (siehe eine der beiden obigen Links) sollte der LogicalOperationStack immer genau 2 Einträge haben, wenn DoLongRunningWork ausgeführt wird (wenn ich eine logische Operation im Haupt-Thread starte, bevor ich DoLongRunningWork mit verschiedenen Techniken starte). Also, mit "beschädigt" meine ich, dass der LogicalOperationStack schließlich mehr als 2 Einträge haben wird.
Von was ich sagen kann, ist dies wahrscheinlich, weil Parallel.For und Parallel.Invoke den Hauptthread als einen der "Worker" -Threads verwenden, um die DoLongRunningWork-Aktion auszuführen.
Die Verwendung eines Stacks, der in CallContext.LogicalSetData gespeichert ist, um das Verhalten von LogicalOperationStack nachzuahmen (ähnlich Log4nets LogicalThreadContext.Stacks, das über CallContext.SetData gespeichert wird), liefert noch schlechtere Ergebnisse. Wenn ich einen solchen Stapel verwende, um den Kontext zu erhalten, wird er in fast allen Szenarien, in denen ich eine "logische Operation" im Haupt-Thread und eine logische Operation in jeder Iteration habe, beschädigt (dh hat nicht die erwartete Anzahl von Einträgen)/Ausführung des DoLongRunningWork-Delegaten.
Ich habe nichts zu bieten, aber ich bin auch an diesem Problem interessiert. Es scheint, dass dieselbe Frage auch für Informationen gilt, die allgemein mit CallContext.LogicalSetData festgelegt wurden, da dies die Technologie ist, die der Trace.CorrelationManager zum Speichern der ActivityId und des LogicalOperationStack verwendet. – wageoghe
@wagageohe - Ich habe es endlich geschafft, dies heute zu testen, habe meine Ergebnisse gepostet :) –
Ich habe einige weitere Details in meiner Antwort gepostet. Ich habe auch einen Link zu einer anderen Antwort hier auf SO gepostet, eine neue Frage, die ich hier auf SO gestellt habe, sowie eine Frage, die ich auf dem Parallel Extensions Forum von Microsoft gestellt habe (aber noch nicht zum 21.01.2011 beantwortet wurde) . Vielleicht finden Sie die Informationen nützlich, vielleicht auch nicht. – wageoghe