C# CorrelationManager.LogicalOperationStack是否与Parallel.For、任务、线程等兼容
有关背景信息,请参见此问题: 这个问题问任务是如何影响的@Greg Samson用一个测试程序回答了自己的问题,该测试程序显示ActivityId在任务上下文中是可靠的。测试程序在任务委托开始时设置ActivityId,休眠以模拟工作,然后在结束时检查ActivityId以确保其值相同(即未被其他线程修改)。程序运行成功 在研究线程、任务和并行操作的其他“上下文”选项(最终为日志记录提供更好的上下文)时,我遇到了一个奇怪的问题(无论如何对我来说很奇怪)。我已将我对他的问题的“答案”复制到下面 我认为它充分描述了我遇到的问题(Trace.CorrelationManager.LogicalOperationStack在Parallel.For的上下文中使用时,显然已损坏或出现了其他问题,但前提是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以确保其值相同(即未被其他线程修改)。程序运行成功 在研究线程、任务和并行操作的其他“上下文”选项(最终为日志记录提供更好的上下文)时,我遇到了一个奇怪的问题(无论如何对我来说很奇怪)。我已将我对他的问题的“答案”复制到下面
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);
}
}
}