Frage

Bevor die Task Parallel-Bibliothek verwenden, habe ich oft CorrelationManager.ActivityId verwendet, um zu verfolgen Tracing / Fehlerberichterstattung mit mehreren Threads.

ActivityId in Thread Local Storage gespeichert, so dass jeder Thread get seine eigene Kopie ist. Die Idee ist, dass, wenn Sie einen Thread (Aktivität) feuern, Sie einen neuen ActivityId zuweisen. Die ActivityId wird mit jedem anderen Trace-Informationen in die Protokolle geschrieben werden, die es ermöglichen, die Trace-Informationen für einen einzelnen ‚Aktivität‘, einzelne aus. Das ist wirklich nützlich mit WCF als die ActivityId kann über an die Servicekomponente getragen werden.

Hier ist ein Beispiel dafür, was ich rede:

static void Main(string[] args)
{
    ThreadPool.QueueUserWorkItem(new WaitCallback((o) =>
    {
        DoWork();
    }));
}

static void DoWork()
{
    try
    {
        Trace.CorrelationManager.ActivityId = Guid.NewGuid();
        //The functions below contain tracing which logs the ActivityID.
        CallFunction1();
        CallFunction2();
        CallFunction3();
    }
    catch (Exception ex)
    {
        Trace.Write(Trace.CorrelationManager.ActivityId + " " + ex.ToString());
    }
}

Jetzt, mit dem TPL, mein Verständnis ist, dass mehrere Aufgaben teilen Themen. Bedeutet dies, dass ActivityId anfällig ist Mitte der Aufgabe neu initialisiert werden (von einer anderen Task)? Gibt es einen neuen Mechanismus mit Aktivität Verfolgung umgehen?

War es hilfreich?

Lösung

lief ich einige Experimente und es stellt sich die Annahme, in meiner Frage aus ist falsch -. Mehr Aufgaben mit dem TPL erstellt nicht auf dem gleichen Thread zur gleichen Zeit ausgeführt wird

Thread-Local Storage sicher mit TPL in .NET 4.0, zu verwenden, da ein Thread nur von einer Aufgabe zu einer Zeit verwendet werden kann.

Die Annahme, dass Aufgaben Threads gemeinsam nutzen gleichzeitig auf einem Interview stützt ich gehörte c # 5.0 DotNetRocks (sorry, ich kann mich nicht erinnern, welche zeigen, es war) - so meine Frage kann (oder auch nicht) relevant bald werden

.

Mein Experiment beginnt eine Reihe von Aufgaben, und zeichnet auf, wie viele Aufgaben liefen, wie lange sie dauerte, und wie wurden viele Threads verbraucht. Der Code ist unten, wenn jemand möchte, sie wiederholen.

class Program
{
    static void Main(string[] args)
    {
        int totalThreads = 100;
        TaskCreationOptions taskCreationOpt = TaskCreationOptions.None;
        Task task = null;
        Stopwatch stopwatch = new Stopwatch();
        stopwatch.Start();
        Task[] allTasks = new Task[totalThreads];
        for (int i = 0; i < totalThreads; i++)
        {
            task = Task.Factory.StartNew(() =>
           {
               DoLongRunningWork();
           }, taskCreationOpt);

            allTasks[i] = task;
        }

        Task.WaitAll(allTasks);
        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.ReadKey();
    }


    private static List<int> threadIds = new List<int>();
    private static object locker = new object();
    private static void DoLongRunningWork()
    {
        lock (locker)
        {
            //Keep a record of the managed thread used.
            if (!threadIds.Contains(Thread.CurrentThread.ManagedThreadId))
                threadIds.Add(Thread.CurrentThread.ManagedThreadId);
        }
        Guid g1 = Guid.NewGuid();
        Trace.CorrelationManager.ActivityId = g1;
        Thread.Sleep(3000);
        Guid g2 = Trace.CorrelationManager.ActivityId;
        Debug.Assert(g1.Equals(g2));
    }
}

Der Ausgang (natürlich diese auf der Maschine abhängt) war:

Completed 100 tasks in 23097 milliseconds
Used 23 threads

Ändern taskCreationOpt zu TaskCreationOptions.LongRunning gab unterschiedliche Ergebnisse:

Completed 100 tasks in 3458 milliseconds 
Used 100 threads

Andere Tipps

Bitte verzeihen Sie meint dies als eine Antwort zu veröffentlichen, da es nicht wirklich Ihre Frage zu beantworten ist, ist es jedoch zu Ihrer Frage, da sie mit CorrelationManager Verhalten befassen und Themen / Aufgaben / etc. Ich habe bei Verwendung der CorrelationManager der LogicalOperationStack (und StartLogicalOperation/StopLogicalOperation Methoden) zusätzlichen Kontext zu schaffen, in Multithreading-Szenarien gesucht.

habe ich dein Beispiel und modifiziert es leicht die Fähigkeit hinzuzufügen arbeiten parallel auszuführen Parallel.For verwenden. Auch ich benutze StartLogicalOperation/StopLogicalOperation an der Halterung (intern) DoLongRunningWork. Konzeptionell DoLongRunningWork tut so etwas wie dies jedes Mal ausgeführt wird:

DoLongRunningWork
  StartLogicalOperation
  Thread.Sleep(3000)
  StopLogicalOperation

Ich habe festgestellt, dass, wenn ich diese logischen Operationen an Ihren Code hinzufügen (mehr oder weniger wie), alle logischen operatins bleiben synchron (immer die erwartete Anzahl von Operationen auf dem Stapel und die Werte der Operationen auf dem Stapel werden immer als erwartet).

In einigen meiner eigenen Tests fand ich, dass dies nicht immer der Fall war. Die logische Operation Stack wurde immer „beschädigt“. Die beste Erklärung, die ich kommen könnte, ist, dass die „Verschmelzung“ Rückseite der Callcontext Information in den „Eltern“ Thread-Kontext, wenn die „Kind“ Thread beendet die „alte“ Information Kind Thread-Kontextes (logische Operation) zu sein "verursachte von einem anderen Geschwister Kind Thread geerbt“.

Das Problem könnte auch auf die Tatsache bezogen sein, daß Parallel.For scheinbar den Haupt-Thread verwendet (zumindest in dem Beispiel Code, wie geschrieben) als eine des „Arbeiter-Threads“ (oder was auch immer sie in der parallel aufgerufen werden soll Domain). Jedes Mal, wenn DoLongRunningWork ausgeführt wird, wird eine neue logische Operation gestartet wird (am Anfang) und gestoppt (am Ende) (das heißt, Aufschieben auf die Logicaloperation und knallen wieder weg von ihnen). Wenn der Haupt-Thread bereits eine logische Operation in Wirkung hat und wenn DoLongRunningWork auf das Gewinde MAIN ausführt, wird eine neue logische Operation so der Hauptthread Logicaloperation begonnen hat nun zwei Operationen. Alle nachfolgenden Ausführungen von DoLongRunningWork (solange diese „Iteration“ von DoLongRunningWork ausgeführt wird auf dem Haupt-Thread) wird (scheinbar) übernehmen die Hauptthread Logicaloperation (die nun zwei Operationen darauf, anstatt nur die eine erwartete Operation).

Es dauerte eine lange Zeit, um herauszufinden, warum das Verhalten der Logicaloperation anders war als in meinem Beispiel als in meiner modifizierten Version von Ihrem Beispiel. Schließlich sah ich, dass in meinem Code habe ich das ganze Programm in einer logischen Operation klammert, während in meiner modifizierten Version des Prüfprogramms ich nicht. Die Implikation ist, dass in meinem Testprogramm, jedes Mal, wenn meine „Arbeit“ wurde durchgeführt (analog zu DoLongRunningWork), gibt es bereits eine logische Operation in Kraft. In meiner modifizierten Version des Testprogrammes, ich hatte nicht das gesamte Programm in einer logischen Operation klammert.

Also, wenn ich Ihr Testprogramm an der Halterung das gesamte Programm in einer logischen Operation geändert und wenn ich Parallel.For verwende, ich lief in genau das gleiche Problem.

das konzeptionelle Modell oben Verwendung, wird dies erfolgreich ausgeführt:

Parallel.For
  DoLongRunningWork
    StartLogicalOperation
    Sleep(3000)
    StopLogicalOperation

Während dies wird schließlich assert aufgrund eines scheinbar nicht synchron Logicaloperation:

StartLogicalOperation
Parallel.For
  DoLongRunningWork
    StartLogicalOperation
    Sleep(3000)
    StopLogicalOperation
StopLogicalOperation

Hier ist mein Beispielprogramm. Es ist ähnlich wie bei Ihnen, dass sie ein DoLongRunningWork Verfahren, dass manipuliert die ActivityId sowie die Logicaloperation hat. Ich habe auch zwei Arten von DoLongRunningWork des Tretens. Ein Aroma verwendet Aufgaben verwendet man Parallel.For. Jeder Geschmack kann auch so ausgeführt werden, dass der gesamte parallelisierten Betrieb in einer logischen Operation eingeschlossen ist oder nicht. So gibt es insgesamt 4 Möglichkeiten, um den Parallelbetrieb auszuführen. Um zu versuchen, jeden einzelnen, einfach uncomment die gewünschte „Use ...“ Methode, recompile, und laufen. UseTasks, UseTasks(true) und UseParallelFor sollten alle completi laufenauf. UseParallelFor(true) wird irgendwann behaupten, weil die Logicaloperation nicht die erwartete Anzahl von Einträgen haben.

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 wird (und / oder anderem Threading / Task-Konstrukte) oder wie kann es wohl verdient seine eigene Frage verwendet werden. Vielleicht werde ich eine Frage stellen. In der Zwischenzeit, frage ich mich, wenn Sie irgendwelche Gedanken dazu haben (oder, ich frage mich, ob Sie darüber nachgedacht hatten mit Logicaloperation seit ActivityId sicher zu sein scheint).

[EDIT]

Sehen Sie meine Antwort auf diese Frage für weitere Informationen über die Verwendung von Logicaloperation und / oder CallContext.LogicalSetData mit einigen der verschiedenen Thema / Thread / Aufgabe / Parallel contstructs.

Siehe auch meine Frage hier auf SO über Logicaloperation und Parallel-Erweiterungen: Is CorrelationManager.LogicalOperationStack kompatibel mit Parallel.For, Aufgaben, Themen, etc.

Schließlich sieht auch meine Frage hier auf Microsofts Parallel Extensions Forum: http: // sozial. msdn.microsoft.com/Forums/en-US/parallelextensions/thread/7c5c3051-133b-4814-9db0-fc0039b4f9d9

In meinen Tests sieht es aus wie Trace.CorrelationManager.LogicalOperationStack beschädigt werden kann, wenn Parallel.For oder Parallel.Invoke verwenden, wenn Sie eine logische Operation in dem Haupt-Thread starten und dann Start / Stopp logische Operationen in den Delegierten. In meinen Tests (siehe eine der beiden Links oben) die Logicaloperation immer genau 2 Einträge haben sollte, wenn DoLongRunningWork ausgeführt wird (wenn ich eine logische Operation in dem Haupt-Thread starten, bevor der DoLongRunningWork mit verschiedenen Techniken zu treten). Also, von „verdorben“ Ich meine, dass die Logicaloperation schließlich viel mehr als 2 Einträge hat.

Von dem, was ich sagen kann, ist dies wahrscheinlich, weil Parallel.For und Parallel.Invoke den Haupt-Thread als eine der „Arbeiter“ Threads verwenden, um die DoLongRunningWork Aktion ausführen.

Mit einem Stapel gespeichert in CallContext.LogicalSetData zu imitieren das Verhalten der Logicaloperation (ähnlich LogicalThreadContext.Stacks log4net ist, die über CallContext.SetData gespeichert ist) ergibt sogar schlechtere Ergebnisse. Wenn ich einen solchen Stapel bin mit Kontext zu halten, wird es beschädigt (dh nicht die erwartete Anzahl der Einträge haben) in fast allen der Szenarien, in denen ich eine „logische Operation“ haben im Hauptthread und eine logische Operation in jeder Iteration / Ausführung des DoLongRunningWork delegieren.

Lizenziert unter: CC-BY-SA mit Zuschreibung
Nicht verbunden mit StackOverflow
scroll top