是CorrelationManager.LogicalOperationStack用的Parallel.For,任务,线程等兼容线程、任务、LogicalOperationStack、Correlat

2023-09-02 01:59:11 作者:不想翻身的咸鱼

请看到这个问题的背景资料:

Please see this question for background information:

How做任务并行库的任务影响ActivityID?

这问题问的任务是如何影响Trace.CorrelationManager.ActivityId. @Greg参孙回敬一个测试程序显示,ActivityId可靠的任务的情况下他自己的问题。测试程序设置一个ActivityId在任务委托的开始,睡觉,以模拟工作,然后检查ActivityId在末尾,以确保它是相同的值(即,它没有被另一个线程修改)。该程序成功运行。

That question asks how Tasks affect Trace.CorrelationManager.ActivityId. @Greg Samson answered his own question with a test program showing that ActivityId is reliable in the context of Tasks. The test program sets an ActivityId at the beginning of the Task delegate, sleeps to simulate work, then checks the ActivityId at the end to make sure that it is the same value (i.e. that it has not been modified by another thread). The program runs successfully.

在研究其他上下文选项线程,任务和并行操作(最终以提供更好的日志记录上下文中),我遇到了一个奇怪的问题,Trace.CorrelationManager.LogicalOperationStack (很奇怪,我反正)。我抄我的答案低于他的问题。

While researching other "context" options for threading, Tasks, and Parallel operations (ultimately to provide better context for logging), I ran into a strange issue with Trace.CorrelationManager.LogicalOperationStack (it was strange to me anyway). I have copied my "answer" to his question below.

我认为,这充分说明,我遇到了这个问题(Trace.CorrelationManager.LogicalOperationStack显然越来越损坏 - 什么 - 中的Parallel.For的上下文中使用时,但前提是的Parallel.For本身是封闭在一个逻辑运算)。

I think that it adequately describes the issue that I ran into (Trace.CorrelationManager.LogicalOperationStack apparently getting corrupted - or something - when used in the context of Parallel.For, but only if the Parallel.For itself is enclosed in a logical operation).

下面是我的问题:

如果Trace.CorrelationManager.LogicalOperationStack是可用的具有的Parallel.For?如果是的话,它应该有所作为,如果用的Parallel.For启动逻辑操作已经生效?

Should Trace.CorrelationManager.LogicalOperationStack be usable with Parallel.For? If so, should it make a difference if a logical operation is already in effect with the Parallel.For is started?

有没有一个正确的方式来使用LogicalOperationStack用的Parallel.For?我可以code此示例程序differntly,使其工程?所谓作品,我的意思是LogicalOperationStack总是有条目的预期数量和项目本身预期的项。

Is there a "correct" way to use LogicalOperationStack with Parallel.For? Could I code this sample program differntly so that it "works"? By "works", I mean that the LogicalOperationStack always has the expected number of entries and the entries themselves are the expected entries.

我已经做了使用线程和线程池线程一些额外的测试,但我会回去,然后重试这些测试,看看我遇到了类似的问题。

I have done some additional testing using Threads and ThreadPool threads, but I would have to go back and retry those tests to see if I ran into similar problems.

我会说,它似乎是工作/并行线程和线程池线程做继承Trace.CorrelationManager.ActivityId和Trace.CorrelationManager.LogicalOperationStack从父线程值。这是预期的,因为这些值由CorrelationManager使用CallContext's LogicalSetData方法(相对于SetData的)。

I will say that it does appear that Task/Parallel threads and ThreadPool threads DO "inherit" the Trace.CorrelationManager.ActivityId and Trace.CorrelationManager.LogicalOperationStack values from the parent thread. This is expected as these values are stored by the CorrelationManager using CallContext's LogicalSetData method (as opposed to SetData).

再次请重提这个问题得到了答案,我下面贴的原始上下文:

Again, please refer back to this question to get the original context for the "answer" that I posted below:

How做任务并行库的任务影响ActivityID?

另请参阅微软并行扩展论坛这个类似的问题(这迄今为止还没有得到回答):

See also this similar question (which so far has not been answered) on Microsoft's Parallel Extensions forum:

http://social.msdn.microsoft.com/Forums/en-US/parallelextensions/thread/7c5c3051-133b-4814-9db0-fc0039b4f9d9

[BEGIN粘贴]

请原谅我的帖子这是一个答案,因为它没有真正回答你的问题,但是,它关系到你的问题,因为它涉及CorrelationManager行为和线程/任务/等。我一直在寻找使用CorrelationManager的 LogicalOperationStack (和 StartLogicalOperation / StopLogicalOperation 方法),以提供多线程方案的其他方面。

Please forgive my posting this as an answer as it is not really answer to your question, however, it is related to your question since it deals with CorrelationManager behavior and threads/tasks/etc. I have been looking at using the CorrelationManager's LogicalOperationStack (and StartLogicalOperation/StopLogicalOperation methods) to provide additional context in multithreading scenarios.

我把你的榜样,并修改了它稍微添加到使用的Parallel.For并行执行工作的能力。另外,我用 StartLogicalOperation / StopLogicalOperation 来支架(内部) DoLongRunningWork 。从概念上讲, DoLongRunningWork 做这样的事情是每次执行时:

I took your example and modified it slightly to add the ability to perform work in parallel using Parallel.For. Also, I use StartLogicalOperation/StopLogicalOperation to bracket (internally) DoLongRunningWork. Conceptually, DoLongRunningWork does something like this each time it is executed:

DoLongRunningWork
  StartLogicalOperation
  Thread.Sleep(3000)
  StopLogicalOperation

我已发现,如果我添加这些逻辑操作到code(或多或少是),所有的逻辑operatins的保持同步(上堆栈操作的始终预期数量和业务的值在堆栈上总是如预期)。

I have found that if I add these logical operations to your code (more or less as is), all of the logical operatins remain in sync (always the expected number of operations on stack and the values of the operations on the stack are always as expected).

在一些我自己的测试中,我发现,这是情况并非总是如此。逻辑运算栈是越来越损坏。我能想出的最好的解释是,合并后的CallContext中的信息为父线程上下文当孩子线程退出是导致老子线程上下文信息(逻辑运算)是继承另一个兄弟子线程。

In some of my own testing I found that this was not always the case. The logical operation stack was getting "corrupted". The best explanation I could come up with is that the "merging" back of the CallContext information into the "parent" thread context when the "child" thread exits was causing the "old" child thread context information (logical operation) to be "inherited" by another sibling child thread.

的问题也可能与该的Parallel.For显然使用主线程(至少在本例中code,作为书面)作为工作线程中的一个(或不管他们应该被称为事实在并行域)。每当DoLongRunningWork被执行时,开始一个新的逻辑操作(在开始时)和停止(结束)(即,压入LogicalOperationStack和弹出退避的话)。如果主线程已经拥有有效的逻辑运算,如果DoLongRunningWork执行主线程,那么一个新的逻辑运算开始,所以主线程的LogicalOperationStack现在有两个操作。将(显然)继承主线程的LogicalOperationStack(现在有两个操作就可以了,而不是只是一个预期的操作)(DoLongRunningWork正在执行主线程,只要这种重复)DoLongRunningWork的任何后续执行。

The problem might also be related to the fact that Parallel.For apparently uses the main thread (at least in the example code, as written) as one of the "worker threads" (or whatever they should be called in the parallel domain). Whenever DoLongRunningWork is executed, a new logical operation is started (at the beginning) and stopped (at the end) (that is, pushed onto the LogicalOperationStack and popped back off of it). If the main thread already has a logical operation in effect and if DoLongRunningWork executes ON THE MAIN THREAD, then a new logical operation is started so the main thread's LogicalOperationStack now has TWO operations. Any subsequent executions of DoLongRunningWork (as long as this "iteration" of DoLongRunningWork is executing on the main thread) will (apparently) inherit the main thread's LogicalOperationStack (which now has two operations on it, rather than just the one expected operation).

我花了很长的时间来弄清楚为什么LogicalOperationStack的行为是不同的在我的例子不是在你的榜样是我修改后的版本。最后,我看到在我的code我在您的测试程序我没有我的修改版本括号内的整个程序中的逻辑运算,而。其含义是在我的测试程序,我的工作,进行各时间(类似于DoLongRunningWork),有已经生效的逻辑运算。在您的测试程序我修改的版本,我还没有括号内的逻辑操作的整个程序。

It took me a long time to figure out why the behavior of the LogicalOperationStack was different in my example than in my modified version of your example. Finally I saw that in my code I had bracketed the entire program in a logical operation, whereas in my modified version of your test program I did not. The implication is that in my test program, each time my "work" was performed (analogous to DoLongRunningWork), there was already a logical operation in effect. In my modified version of your test program, I had not bracketed the entire program in a logical operation.

所以,当我修改了测试程序,可以涵盖整个程序我使用的Parallel.For在逻辑操作并且,我遇到了一模一样的问题。

So, when I modified your test program to bracket the entire program in a logical operation AND if I am using Parallel.For, I ran into exactly the same problem.

使用上面的概念模型,这将成功运行:

Using the conceptual model above, this will run successfully:

Parallel.For
  DoLongRunningWork
    StartLogicalOperation
    Sleep(3000)
    StopLogicalOperation

虽然这最终会因断言到一个明显的不同步LogicalOperationStack的:

While this will eventually assert due to an apparently out of sync LogicalOperationStack:

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

下面是我的示例程序。它是与你相似,它具有一个DoLongRunningWork方法操纵ActivityId以及LogicalOperationStack。我也有踢DoLongRunningWork的两种口味。一个特性使用任务之一使用的Parallel.For。每个香味也可以被执行,使得整个并行操作被封闭在一个逻辑操作或没有。因此,总共有4种方式来执行并行操作的。为了每一个尝试,只需取消注释所需的使用......的方法,重新编译和运行。 UseTasks UseTasks(真) UseParallelFor 应全部运行完毕。 UseParallelFor(真)将肯定在​​某些时候,因为LogicalOperationStack没有条目的期望值。

Here is my sample program. It is similar to yours in that it has a DoLongRunningWork method that manipulates the ActivityId as well as the LogicalOperationStack. I also have two flavors of kicking of DoLongRunningWork. One flavor uses Tasks one uses Parallel.For. Each flavor can also be executed such that the whole parallelized operation is enclosed in a logical operation or not. So, there are a total of 4 ways to execute the parallel operation. To try each one, simply uncomment the desired "Use..." method, recompile, and run. UseTasks, UseTasks(true), and UseParallelFor should all run to completion. UseParallelFor(true) will assert at some point because the LogicalOperationStack does not have the expected number of entries.

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();
    }

  } 
}

如果LogicalOperationStack可以的Parallel.For使用的这整个问题(和/或其它线程/任务构造),或者如何将其可能使用的优点它自己的问题。也许我会后一个问题。在此期间,我不知道你对此有什么想法(或者,我不知道你使用LogicalOperationStack因为ActivityId似乎是安全的考虑过)。

This whole issue of if LogicalOperationStack can be used with Parallel.For (and/or other threading/Task constructs) or how it can be used probably merits its own question. Maybe I will post a question. In the meantime, I wonder if you have any thoughts on this (or, I wonder if you had considered using LogicalOperationStack since ActivityId appears to be safe).

[糊底]

有没有人有任何想法就这个问题?

Does anyone have any thoughts on this issue?

推荐答案

[开始更新]

我也问过这个问题上微软的并行扩展对.NET的支持论坛并最终获得了从斯蒂芬Toub 的答案。原来有有一个在LogicalCallContext 这个bug是造成LogicalOperationStack被损坏。还有一个很好的说明(在后续由斯蒂芬的答复,我给他的回答让),其给出的Parallel.For是如何工作的有关少量发放任务,为什么这使得的Parallel.For容易受到漏洞的简要overiew。

I also asked this question on Microsoft's Parallel Extensions for .Net support forum and eventually received an answer from Stephen Toub. It turns out there there is a bug in the LogicalCallContext that is causing the LogicalOperationStack to be corrupted. There is also a nice description (in a followup by Stephen to a reply that I made to his answer) that gives a brief overiew of how Parallel.For works regarding doling out Tasks and why that makes Parallel.For susceptible to the bug.

在我的回答下面我推测,LogicalOperationStack不符合的Parallel.For兼容,因为的Parallel.For使用主线程的工人线程之一。根据斯蒂芬的解释,我的猜测是不正确的。的Parallel.For确实使用主线程为工人线程之一,但它不是简单地使用原样。第一项任务是运行在主线程上,但是运行在这样一种方式,这是因为如果它运行在一个新的线程。阅读斯蒂芬的描述获取更多信息。

In my answer below I speculate that LogicalOperationStack is not compatible with Parallel.For because Parallel.For uses the main thread as one of the "worker" threads. Based on Stephen's explanation, my speculation was incorrect. Parallel.For does use the main thread as one of the "worker" threads, but it is not simply used "as is". The first Task is run on the main thread, but is run in such a way that it is as if it is run on a new thread. Read Stephen's description for more info.

[结束更新]

这是我可以告诉,答案如下:

From what I can tell, the answer is as follows:

无论ActivityId和LogicalOperationStack通过 CallContext.LogicalSetData 。这意味着这些值将流到任何孩子主题。这是pretty的酷,你可以,例如,设置ActivityId在入口点进入一个多线程的服务器(比如服务呼叫),并最终被从该入口点的所有主题可以是相同的活动的一部分, 。同样地,逻辑运算(经由LogicalOperationStack)也流到子线程

Both ActivityId and LogicalOperationStack are stored via CallContext.LogicalSetData. That means that these values will be "flowed" to any "child" threads. That is pretty cool as you could, for example, set ActivityId at the entry point into a multithreaded server (say a service call) and all threads that are ultimately started from that entry point can be part of the same "activity". Similarly, logical operations (via the LogicalOperationStack) also flow to the child threads.

至于Trace.CorrelationManager.ActivityId:

With regards to Trace.CorrelationManager.ActivityId:

ActivityId似乎与我所用测试过的所有线程模型兼容:直接使用线程,使用线程池,使用任务,使用并行*。在所有的情况下,ActivityId具有预期值。

ActivityId seems to be compatible with all threading models that I have tested it with: Using threads directly, using ThreadPool, using Tasks, using Parallel.*. In all cases, ActivityId has the expected value.

至于Trace.CorrelationManager.LogicalOperationStack:

With regards to Trace.CorrelationManager.LogicalOperationStack:

LogicalOperationStack似乎与大多数线程模型兼容,但不能与并行。*。使用直接线程,线程池和任务,在LogicalOperationStack(如操纵在我的问题提供样品code)保持其完整性。在任何时候都LogicalOperationStack的内容符合市场预期。

LogicalOperationStack seems to be compatible with most threading models, but NOT with Parallel.*. Using threads directly, ThreadPool, and Tasks, the LogicalOperationStack (as manipulated in the sample code provided in my question) maintains its integrity. At all times the contents of the LogicalOperationStack is as expected.

LogicalOperationStack是不符合的Parallel.For兼容。如果一个逻辑运算是有效,也就是如果已经称为CorrelationManager.StartLogicalOperation,开始并行之前。*操作,然后开始在Paralle的上下文中一个新的逻辑运算。*(即在委托) ,那么LogicalOperationStack将被破坏。 (我应该说,它可能会被损坏。并行。*可能不会产生任何额外的线程,这意味着LogicalOperationStack将是安全的)。

LogicalOperationStack is NOT compatible with Parallel.For. If a logical operation is "in effect", that is if you have called CorrelationManager.StartLogicalOperation, prior to starting the Parallel.* operation and then you start a new logical operation in the context of the Paralle.* (i.e. in the delegate), then the LogicalOperationStack WILL be corrupted. (I should say that it will PROBABLY be corrupted. Parallel.* might not create any additional threads, which means that the LogicalOperationStack would be safe).

问题来源于水货。*使用主线程(或者可能更准确,启动并联运行的线程)作为它的工人线程之一的事实。这意味着,作为逻辑行动启动和停止的工人线程是一样的主线程,主线程的LogicalOperationStack被修改。即使调用code(即委托)正确地维护栈(确保每个StartLogicalOperation与相应StopLogicalOperation停止),主线程的堆栈被修改。最终,它似乎(对我来说,反正),即主线程的LogicalOperationStack基本上是由两个不同的逻辑线程修改:主线程和一个工人线程,这两者恰好是相同的主题。

The problem stems from the fact that Parallel.* uses the main thread (or, probably more correctly, the thread that starts the parallel operation) as one of its "worker" threads. That means that as "logical operations" are started and stopped in the "worker" thread that is the same as the "main" thread, the "main" thread's LogicalOperationStack is being modified. Even if the calling code (i.e. the delegate) maintains the stack correctly (ensuring that each StartLogicalOperation is "stopped" with a corresponding StopLogicalOperation), the "main" threads stack is modified. Ultimately it seems (to me, anyway), that the LogicalOperationStack of the "main" thread is essentially being modified by two different "logical" threads: the "main" thread and a "worker" thread, which both happen to be the SAME thread.

我不知道究竟为什么这个深跌细节不工作(至少我希望它的工作)。我最好的猜测是,委托一个线程每次执行(即不一样的主线程),线程继承主线程的LogicalOperationStack的当前状态。如果代表当前正在执行的主线程(被重用为辅助线程),并且已经开始了逻辑运算的其他并行的代表,那么一个(或多个),将继承主线程的LogicalOperationStack,现在有在效果的一个(或多个)新的逻辑操作!

I don't know the deep down specifics of exactly why this is not working (at least as I would expect it work). My best guess is that each time the delegate is executed on a thread (that is not the same as the main thread), the thread "inherits" the current state of the main thread's LogicalOperationStack. If the delegate is currently executing on the main thread (being reused as a worker thread), and has started a logical operation, then one (or more than one) of the other parallelized delegates will "inherit" the main thread's LogicalOperationStack that now has one (or more) new logical operations in effect!

FWIW,我实现(主要用于测试,我没有真正使用它的那一刻),下面的逻辑栈,以模仿LogicalOperationStack,但这样做在这样一种方式,它会与并行。*感受免费尝试一下,和/或使用。为了测试,更换呼叫

FWIW, I implemented (mainly for testing, I am not actually using it at the moment), the following "logical stack" to mimic the LogicalOperationStack, but do it in such a way that it will work with Parallel.* Feel free to try it out and/or use it. To test, replace the calls to

Trace.CorrelationManager.StartLogicalOperation/StopLogicalOperation

在从我的调用原来​​问题的样本code到

in the sample code from my original question with calls to

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
  }
}

这个灵感来自于这里由布伦特VanderMeide记载的范围对象: HTTP:// WWW .dnrtv.com / Default.aspx的?showNum = 114

This was inspired by the scope objects described by Brent VanderMeide here: http://www.dnrtv.com/default.aspx?showNum=114

您可以使用这个类是这样的:

You could use this class like this:

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");
  }
}