答案 0 :(得分:5)
[开始更新]
我还在Microsoft's Parallel Extensions for .Net support forum上提出了这个问题,最终收到了answer from Stephen Toub。事实证明,有一个bug in the LogicalCallContext导致LogicalOperationStack被破坏。还有一个很好的描述(在Stephen的回复中,我对他的回答做了回复),简要介绍了Parallel.For如何处理任务以及为什么使Parallel.For容易受到bug的影响。< / p>
在下面的回答中,我推测LogicalOperationStack与Parallel.For不兼容,因为Parallel.For使用主线程作为“工作”线程之一。根据斯蒂芬的解释,我的推测是错误的。 Parallel.For确实使用主线程作为“工作”线程之一,但它不是简单地“按原样”使用。第一个Task在主线程上运行,但运行方式就好像它在新线程上运行一样。阅读斯蒂芬的描述以获取更多信息。
[结束更新]
据我所知,答案如下:
ActivityId和LogicalOperationStack都通过CallContext.LogicalSetData存储。这意味着这些值将“流向”任何“子”线程。这很酷,例如,可以在入口点将ActivityId设置为多线程服务器(比如服务调用),并且最终从该入口点启动的所有线程都可以是同一“活动”的一部分。同样,逻辑操作(通过LogicalOperationStack)也会流向子线程。
关于Trace.CorrelationManager.ActivityId:
ActivityId似乎与我测试过的所有线程模型兼容:直接使用线程,使用ThreadPool,使用Tasks,使用Parallel。*。在所有情况下,ActivityId都具有预期值。
关于Trace.CorrelationManager.LogicalOperationStack:
LogicalOperationStack似乎与大多数线程模型兼容,但不兼容Parallel。*。直接使用线程,ThreadPool和Tasks,LogicalOperationStack(在我的问题中提供的示例代码中操作)保持其完整性。在任何时候LogicalOperationStack的内容都是预期的。
LogicalOperationStack与Parallel.For不兼容。如果逻辑操作“有效”,即在调用Parallel。*操作之前调用CorrelationManager.StartLogicalOperation,然后在Paralle。*(即代理中)的上下文中启动新的逻辑操作。 ,然后LogicalOperationStack将被破坏。 (我应该说它可能会被破坏。并行。*可能不会创建任何额外的线程,这意味着LogicalOperationStack将是安全的。)
问题源于Parallel。*使用主线程(或者更准确地说,启动并行操作的线程)作为其“工作”线程之一的事实。这意味着当“逻辑操作”在与“主”线程相同的“工作”线程中启动和停止时,正在修改“主”线程的LogicalOperationStack。即使调用代码(即委托)正确地维护堆栈(确保每个StartLogicalOperation都使用相应的StopLogicalOperation“停止”),也会修改“主”线程堆栈。最终似乎(对我而言),“主”线程的LogicalOperationStack基本上被两个不同的“逻辑”线程修改:“主”线程和“工作”线程,它们都恰好是相同的线程。
我不知道究竟为什么这不起作用的深层细节(至少我期望它起作用)。我最好的猜测是每次委托在一个线程上执行(与主线程不同),线程“继承”主线程的LogicalOperationStack的当前状态。如果委托当前正在主线程上执行(被重用为工作线程),并且已经开始逻辑操作,那么其他并行化委托中的一个(或多个)将“继承”现在具有的主线程的LogicalOperationStack一个(或多个)新逻辑操作生效!
FWIW,我实现了(主要用于测试,我目前还没有实际使用它),下面的“逻辑堆栈”来模仿LogicalOperationStack,但这样做的方式是它可以与Parallel一起使用。* Feel免费试用和/或使用它。要测试,请替换
的调用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
}
}
这受到Brent VanderMeide在此描述的范围对象的启发:http://www.dnrtv.com/default.aspx?showNum=114
您可以像这样使用此类:
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");
}
}
答案 1 :(得分:2)
我正在研究一种逻辑堆栈的方法,该堆栈应该在使用TPL的应用程序中轻松工作。我决定使用LogicalOperationStack,因为它在不改变现有代码的情况下完成了我需要的所有工作。但后来我读到了LogicalCallContext中的一个错误:
所以我试图找到这个bug的解决方法,我想我让它为TPL工作(谢谢ILSpy):
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);
}
}
}