C# CorrelationManager.LogicalOperationStack是否与Parallel.For、任务、线程等兼容

C# CorrelationManager.LogicalOperationStack是否与Parallel.For、任务、线程等兼容,c#,.net,system.diagnostics,task-parallel-library,parallel-extensions,C#,.net,System.diagnostics,Task Parallel Library,Parallel Extensions,有关背景信息,请参见此问题: 这个问题问任务是如何影响的@Greg Samson用一个测试程序回答了自己的问题,该测试程序显示ActivityId在任务上下文中是可靠的。测试程序在任务委托开始时设置ActivityId,休眠以模拟工作,然后在结束时检查ActivityId以确保其值相同(即未被其他线程修改)。程序运行成功 在研究线程、任务和并行操作的其他“上下文”选项(最终为日志记录提供更好的上下文)时,我遇到了一个奇怪的问题(无论如何对我来说很奇怪)。我已将我对他的问题的“答案”复制到下面

有关背景信息,请参见此问题:

这个问题问任务是如何影响的@Greg Samson用一个测试程序回答了自己的问题,该测试程序显示ActivityId在任务上下文中是可靠的。测试程序在任务委托开始时设置ActivityId,休眠以模拟工作,然后在结束时检查ActivityId以确保其值相同(即未被其他线程修改)。程序运行成功

在研究线程、任务和并行操作的其他“上下文”选项(最终为日志记录提供更好的上下文)时,我遇到了一个奇怪的问题(无论如何对我来说很奇怪)。我已将我对他的问题的“答案”复制到下面

我认为它充分描述了我遇到的问题(Trace.CorrelationManager.LogicalOperationStack在Parallel.For的上下文中使用时,显然已损坏或出现了其他问题,但前提是Parallel.For本身包含在逻辑操作中)

以下是我的问题:

  • Trace.CorrelationManager.LogicalOperationStack是否应与Parallel.For一起使用?如果是这样的话,那么如果一个逻辑操作已经和Parallel.For一起生效,它是否会产生影响呢

  • 是否有一种“正确”的方法将LogicalOperationStack与Parallel一起使用?我可以对这个示例程序进行不同的编码以使其“工作”吗?所谓“工作”,我的意思是LogicalOperationStack始终具有预期的条目数,而条目本身就是预期的条目

  • 我使用线程和线程池线程做了一些额外的测试,但我必须返回并重试这些测试,以查看是否遇到类似的问题

    我要说的是,任务/并行线程和线程池线程似乎确实从父线程“继承”了Trace.CorrelationManager.ActivityId和Trace.CorrelationManager.LogicalOperationStack值。这是预期的,因为CorrelationManager使用的LogicalSetData方法(与SetData相反)存储这些值

    同样,请回到这个问题,以获得我在下面发布的“答案”的原始上下文:

    另请参见Microsoft并行扩展论坛上的类似问题(目前尚未回答):

    [开始粘贴]

    请原谅我将此作为答案发布,因为它不是对您问题的真正答案,但是,它与您的问题相关,因为它涉及CorrelationManager行为和线程/任务等。我一直在研究如何使用CorrelationManager的
    LogicalOperationStack
    (和
    StartLogicalOperation/StopLogicalOperation
    方法),以在多线程场景中提供附加上下文

    我以您的示例为例,对其进行了轻微修改,添加了使用parallel.For并行执行工作的功能。此外,我使用
    StartLogicalOperation/StopLogicalOperation
    来括起(内部)
    DoLongRunningWork
    。从概念上讲,
    DoLongRunningWork
    每次执行时都会执行类似的操作:

    DoLongRunningWork
      StartLogicalOperation
      Thread.Sleep(3000)
      StopLogicalOperation
    
    我发现,如果我将这些逻辑操作添加到代码中(或多或少保持原样),则所有逻辑操作都保持同步(堆栈上的操作数和堆栈上的操作值始终与预期值相同)

    在我自己的一些测试中,我发现情况并非总是如此。逻辑操作堆栈正在“损坏”。我能想到的最好解释是,当“子”线程退出时,CallContext信息的“合并”回“父”线程上下文导致了“旧”子线程上下文信息(逻辑操作)由另一个同级子线程“继承”

    问题还可能与以下事实有关:Parallel.For显然使用主线程(至少在编写的示例代码中)作为一个“工作线程”(或在并行域中调用的任何线程)。每当执行DoLongRunningWork时,都会启动(开始)并停止一个新的逻辑操作(在末尾)(即推到LogicalOperationStack上并弹出)。如果主线程已经有一个有效的逻辑操作,并且如果在主线程上执行DoLongRunningWork,则会启动一个新的逻辑操作,以便主线程的LogicalOperationStack现在有两个操作。DoLongRunningWork的任何后续执行(只要此“迭代”DoLongRunningWork在主线程上执行时,将(显然)继承主线程的LogicalOperationStack(它现在有两个操作,而不仅仅是一个预期的操作)

    我花了很长时间才弄明白为什么我的示例中的LogicalOperationStack的行为与您的示例的修改版本中的不同。最后,我发现在我的代码中,我将整个程序括在了一个逻辑操作中,而在您的测试程序的修改版本中,我没有。这意味着在我的测试中rogram,每次执行我的“工作”(类似于DoLongRunningWork)时,已经有一个有效的逻辑操作。在我修改的测试程序版本中,我没有将整个程序括在一个逻辑操作中

    所以,当我修改你的测试程序,把整个程序放在一个逻辑运算中,如果我使用Parallel.For,我遇到了完全相同的问题

    使用上述概念模型,将成功运行:

    Parallel.For
      DoLongRunningWork
        StartLogicalOperation
        Sleep(3000)
        StopLogicalOperation
    
    由于LogicalOperationStack明显不同步,最终将断言:

    StartLogicalOperation
    Parallel.For
      DoLongRunningWork
        StartLogicalOperation
        Sleep(3000)
        StopLogicalOperation
    StopLogicalOperation
    
    这是我的示例程序。它与您的类似,它有一个DoLongRunningWork方法,可以操作ActivityId和LogicalOperationStack。我还有两种DoLongRun的风格
    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();
        }
    
      } 
    }
    
    Trace.CorrelationManager.StartLogicalOperation/StopLogicalOperation
    
    LogicalOperation.OperationStack.Push()/Pop().
    
    
    //OperationStack.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    using System.Runtime.Remoting.Messaging;
    
    namespace LogicalOperation
    {
      public static class OperationStack
      {
        private const string OperationStackSlot = "OperationStackSlot";
    
        public static IDisposable Push(string operation)
        {
          OperationStackItem parent = CallContext.LogicalGetData(OperationStackSlot) as OperationStackItem;
          OperationStackItem op = new OperationStackItem(parent, operation);
          CallContext.LogicalSetData(OperationStackSlot, op);
          return op;
        }
    
        public static object Pop()
        {
          OperationStackItem current = CallContext.LogicalGetData(OperationStackSlot) as OperationStackItem;
    
          if (current != null)
          {
            CallContext.LogicalSetData(OperationStackSlot, current.Parent);
            return current.Operation;
          }
          else
          {
            CallContext.FreeNamedDataSlot(OperationStackSlot);
          }
          return null;
        }
    
        public static object Peek()
        {
          OperationStackItem top = Top();
          return top != null ? top.Operation : null;
        }
    
        internal static OperationStackItem Top()
        {
          OperationStackItem top = CallContext.LogicalGetData(OperationStackSlot) as OperationStackItem;
          return top;
        }
    
        public static IEnumerable<object> Operations()
        {
          OperationStackItem current = Top();
          while (current != null)
          {
            yield return current.Operation;
            current = current.Parent;
          }
        }
    
        public static int Count
        {
          get
          {
            OperationStackItem top = Top();
            return top == null ? 0 : top.Depth;
          }
        }
    
        public static IEnumerable<string> OperationStrings()
        {
          foreach (object o in Operations())
          {
            yield return o.ToString();
          }
        }
      }
    }
    
    
    //OperationStackItem.cs
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    
    namespace LogicalOperation
    {
      public class OperationStackItem : IDisposable
      {
        private OperationStackItem parent = null;
        private object operation;
        private int depth;
        private bool disposed = false;
    
        internal OperationStackItem(OperationStackItem parentOperation, object operation)
        {
          parent = parentOperation;
          this.operation = operation;
          depth = parent == null ? 1 : parent.Depth + 1;
        }
    
        internal object Operation { get { return operation; } }
        internal int Depth { get { return depth; } }
    
        internal OperationStackItem Parent { get { return parent; } }
    
        public override string ToString()
        {
          return operation != null ? operation.ToString() : "";
        }
    
        #region IDisposable Members
    
        public void Dispose()
        {
          if (disposed) return;
    
          OperationStack.Pop();
    
          disposed = true;
        }
    
        #endregion
      }
    }
    
    public void MyFunc()
    {
      using (LogicalOperation.OperationStack.Push("MyFunc"))
      {
        MyOtherFunc();
      }
    }
    
    public void MyOtherFunc()
    {
      using (LogicalOperation.OperationStack.Push("MyOtherFunc"))
      {
        MyFinalFunc();
      }
    }
    
    public void MyFinalFunc()
    {
      using (LogicalOperation.OperationStack.Push("MyFinalFunc"))
      {
        Console.WriteLine("Hello");
      }
    }
    
    public static class FixLogicalOperationStackBug
    {
        private static bool _fixed = false;
    
        public static void Fix()
        {
            if (!_fixed)
            {
                _fixed = true;
    
                Type taskType = typeof(Task);
                var s_ecCallbackField = taskType.GetFields(BindingFlags.Static | BindingFlags.NonPublic).First(f => f.Name == "s_ecCallback");
                ContextCallback s_ecCallback = (ContextCallback)s_ecCallbackField.GetValue(null);
    
                ContextCallback injectedCallback = new ContextCallback(obj =>
                {
                    // Next line will set the private field m_IsCorrelationMgr of LogicalCallContext which isn't cloned
                    CallContext.LogicalSetData("System.Diagnostics.Trace.CorrelationManagerSlot", Trace.CorrelationManager.LogicalOperationStack);
                    s_ecCallback(obj);
                });
    
                s_ecCallbackField.SetValue(null, injectedCallback);
            }
        }
    }