CorrelationManager.LogicalOperationStack是否与Parallel.For,Tasks,Threads等兼容

时间:2011-01-18 21:53:20

标签: c# .net system.diagnostics task-parallel-library parallel-extensions

2 个答案:

答案 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中的一个错误:

https://connect.microsoft.com/VisualStudio/feedback/details/609929/logicalcallcontext-clone-bug-when-correlationmanager-slot-is-present

所以我试图找到这个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);
        }
    }
}