当前位置: 首页 > 编程语言 > C#

任务并行库中的任务如何影响ActivityID?分享

时间:2023-04-11 01:54:57 C#

TaskParallel库中的任务如何影响ActivityID?在使用TaskParallel库之前,我经常使用CorrelationManager.ActivityId进行多线程跟踪/错误报告。ActivityId存储在线程本地存储中,因此每个线程都有自己的副本。这个想法是,当你启动一个线程(活动)时,你分配一个新的ActivityId。ActivityId将与任何其他跟踪信息一起写入日志,从而可以单独列出单个“活动”的跟踪信息。这对于WCF来说非常有用,因为ActivityId可以传输到服务组件。这是我正在谈论的示例:staticvoidMain(string[]args){ThreadPool.QueueUserWorkItem(newWaitCallback((o)=>{DoWork();}));}staticvoidDoWork(){try{Trace.CorrelationManager.ActivityId=Guid.NewGuid();//以下函数包含记录ActivityID的跟踪。调用函数1();调用函数2();调用函数3();}catch(Exceptionex){Trace.Write(Trace.CorrelationManager.ActivityId+""+ex.ToString());现在,通过TPL,我的理解是多个任务共享线程。这是否意味着ActivityId很容易在任务中间(由另一个任务)重新初始化?是否有处理活动跟踪的新机制?我进行了一些实验,结果证明我问题中的假设是错误的——使用TPL创建的多个任务不会同时在同一个线程上运行。ThreadLocalStorage可以安全地与.NET4.0中的TPL一起使用,因为一个线程一次只能由一个任务使用。任务可以并发共享线程的假设是基于我在DotNetRocks上听到的关于c#5.0的采访(抱歉,我不记得是哪个节目)——所以我的问题可能(或可能不会)很快变得相关。我的实验启动了多个任务,记录了运行了多少个任务,耗时多少,消耗了多少线程。如果有人想重复它,代码如下。类程序{staticvoidMain(string[]args){inttotalThreads=100;TaskCreationOptionstaskCreationOpt=TaskCreationOptions.None;任务task=null;秒表stopwatch=newStopwatch();秒表.Start();Task[]allTask??s=newTask[totalThreads];对于(inti=0;i{DoLongRunningWork();},taskCreationOpt);allTask??s[i]=任务;}Task.WaitAll(allTask??s);秒表.Stop();Console.WriteLine(String.Format("在{1}毫秒内完成了{0}个任务",totalThreads,stopwatch.ElapsedMilliseconds));Console.WriteLine(String.Format("使用了{0}个线程",threadIds.Count));控制台.ReadKey();}privatestaticListthreadIds=newList();私有静态对象储物柜=新对象();privatestaticvoidDoLongRunningWork(){lock(locker){//记录使用的托管线程。如果(!threadIds.Contains(Thread.CurrentThread.ManagedThreadId))threadIds.Add(Thread.CurrentThread.ManagedThreadId);}Guidg1=Guid.NewGuid();Trace.CorrelationManager.AactivityId=g1;线程.睡眠(3000);Guidg2=Trace.CorrelationManager.ActivityId;Debug.Assert(g1.Equals(g2));输出(当然这取决于机器)是:Completed100tasksin23097millisecondsUsed23threads一个答案,因为它并不是你问题的真正答案,但是,它与你的问题有关因为它处理CorrelationManager行为和线程/任务/等我一直在寻找使用CorrelationManager的LogicalOperationStack(和StartLogicalOperation/StopLogicalOperation方法)来提供额外的上下文多线程场景。我采用了您的示例并稍作修改以添加使用Parallel.For并行执行工作的能力。此外,我使用StartLogicalOperation/StopLogicalOperation来括起(内部)DoLongRunningWork。从概念上讲,DoLongRunningWork每次执行时都会执行以下操作:DoLongRunningWorkStartLogicalOperationThread.Sleep(3000)StopLogicalOperation我发现如果我将这些逻辑操作添加到您的代码中(或多或少),所有逻辑操作都将保持同步(操作数总是期望在堆栈,并且堆栈上的操作数值始终是预期的)。在我自己的一些测试中,我发现情况并非总是如此。逻辑操作的堆栈是“腐败的”。我能想到的最好的解释是,当“子”线程退出时,将CallContext信息“合并”回“父”线程上下文,导致“旧”子线程上下文信息(逻辑操作)被“继承”"由另一个兄弟线程。该问题也可能与Parallel.For显然使用主线程(至少在示例代码中,如所写)作为“工作线程”之一(或者它们应该在并行域中调用)这一事实有关。每当执行DoLongRunningWork时,都会启动一个新的逻辑操作(在开始时)并停止(在结束时)(即,推入和弹出LogicalOperationStack)。如果主线程已经有一个有效的逻辑操作,DoLongRunningWork在主线程上执行,开始一个新的逻辑操作,所以主线程的LogicalOperationStack现在有两个操作。DoLongRunningWork的任何后续执行(只要DoLongRunningWork的此“迭代”在主线程上执行)将(显然)继承主线程的LogicalOperationStack(现在它有两个操作,而不是预期的只有一个)。我花了很长时间才弄清楚为什么LogicalOperationStack在我的示例中的行为与我的示例的修改版本不同。最后,我看到在我的代码中我将整个程序包含在一个逻辑操作中,而在我的测试程序的修改版本中我不是。这意味着在我的测试程序中,每次执行“工作”(类似于DoLongRunningWork)时,已经有一个逻辑操作。在我的测试程序的修改版本中,我没有将整个程序包含在逻辑操作中。因此,当我修改您的测试程序以将整个程序包含在逻辑操作中时,如果我使用Parallel.For,我会遇到完全相同的问题。使用上面的概念模型,这将成功运行:Parallel.ForDoLongRunningWorkStartLogicalOperationSleep(3000)StopLogicalOperation虽然这最终会断言,因为LogicalOperationStack显然不同步:StartLogicalOperationParallel.ForDoLongRunningWorkStartLogicalOperationSleep(3000)StopLogicalOperationStopLogicalOperation这是我的示例程序。它与您的类似,因为它有一个DoLongRunningWork方法来操作ActivityId以及LogicalOperationStack。我还有两种方法可以启动DoLongRunningWork。一种风格使用使用Parallel.For的任务一。还可以实现每种样式,使整个并行操作包含或不包含在逻辑操作中。所以总共有4种方法来执行并行操作。要尝试每一个,只需取消注释所需的“使用...”方法,重新编译并运行。UseTasks、UseTasks(true)和UseParallelFor都应该运行到完成。UseParallelFor(true)将在某个时候断言,因为LogicalOperationStack没有预期的条目数。使用系统;使用System.Collections.Generic;使用System.Linq;使用系统文本;使用系统诊断;使用系统线程;使用System.Threading.Tasks;namespaceCorrelationManagerParallelTest{classProgram{staticvoidMain(string[]args){//UseParallelFor(true)将断言,因为LogicalOperationStack不会有预期的//条目数,所有其他条目将运行完成。使用任务();//相当于原始测试程序,只有并行化//逻辑运算中包含的操作。////使用任务(真);//在逻辑操作中括号整个UseTasks方法////UseParallelFor();//等同于原始测试程序,但使用Parallel.For而不是Tasks。仅括号中的逻辑操作中的并行化//操作。////使用并行(真);//逻辑运算中括号整个UseParallelFor方法}privatestaticListthreadIds=newList();私有静态对象储物柜=新对象();私人静态我ntmainThreadId=Thread.CurrentThread.ManagedThreadId;私人静态intmainThreadUsedInDelegate=0;//baseCount是DoLongRunningWork启动时//LogicalOperationStack中的预期条目数。如果整个操作被Start/StopLogicalOperation在外部括起来,则baseCount将为1。否则,//它将为0。privatestaticvoidDoLongRunningWork(intbaseCount){lock(locker){//保留记录使用托管线程。如果(!threadIds.Contains(Thread.CurrentThread.ManagedThreadId))threadIds.Add(Thread.CurrentThread.ManagedThreadId);如果(Thread.CurrentThread.ManagedThreadId==mainThreadId){mainThreadUsedInDelegate++;}}Guidlo1=Guid.NewGuid();Trace.CorrelationManager.StartLogicalOperation(lo1);Guidg1=Guid.NewGuid();Trace.CorrelationManager.ActivityId=g1;线程.睡眠(3000);Guidg2=Trace.CorrelationManager.ActivityId;Debug.Assert(g1.Equals(g2));//这个断言,LogicalOperation.Count,如果在Parallel.For操作开始时有逻辑操作//有效,最终将失败。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();}privatestaticvoidUseTasks(boolencloseInLogicalOperation=false){inttotalThreads=100;TaskCreationOptionstaskCreationOpt=TaskCreationOptions.None;任务task=null;秒表stopwatch=newStopwatch();跑表。开始();如果(encloseInLogicalOperation){Trace.CorrelationManager.StartLogicalOperation();}Task[]allTask??s=newTask[totalThreads];对于(inti=0;i{DoLongRunningWork(encloseInLogicalOperation?1:0);},taskCreationOpt);allTask??s[i]=任务;}Task.WaitAll(allTask??s);如果(encloseInLogicalOperation){Trace.CorrelationManager.StopLogicalOperation();}秒表.Stop();Console.WriteLine(String.Format("在{1}毫秒内完成了{0}个任务",totalThreads,stopwatch.ElapsedMilliseconds));Console.WriteLine(String.Format("使用了{0}个线程",threadIds.Count));Console.WriteLine(String.Format("主线程在委托中使用{0}次",mainThreadUsedInDelegate));控制台.ReadKey();}privatestaticvoidUseParallelFor(boolencloseInLogicalOperation=false){inttotalThreads=100;秒表stopwatch=newStopwatch();秒表.Start();如果(encloseInLogicalOperation){Trace.CorrelationManager.StartLogicalOperation();}Parallel.For(0,totalThreads,i=>{DoLongRunningWork(encloseInLogicalOperation?1:0);});如果(encloseInLogicalOperation){Trace.CorrelationManager.StopLogicalOperation();}秒表.Stop();Console.WriteLine(String.Format("在{1}毫秒内完成了{0}个任务",totalThreads,stopwatch.ElapsedMilliseconds));Console.WriteLine(String.Format("使用了{0}个线程",threadIds.Count));Console.WriteLine(String.Format("主线程在委托中使用{0}次",mainThreadUsedInDelegate));控制台.ReadKey();如果LogicalOperationStack可以与Parallel.For(和/或其他线程/任务构造)一起使用,那么关于如何使用它的整个问题可能是值得的问你自己的问题也许我会发布一个问题。与此同时,我想知道您是否对此有任何想法(或者,我想知道您是否考虑过使用LogicalOperationStack,因为ActivityId似乎是安全的)。[编辑]有关将LogicalOperationStack和/或CallContext.LogicalSetData与各种Thread/ThreadPool/Task/Parallel构造一起使用的详细信息,请参阅我对这个问题的回答。另请参阅我在此处关于LogicalOperationStack和并行扩展的问题:IsCorrelationManager.LogicalOperationStackcompatiblewithParallel.For,Tasks,Threads,etc.最后,在Microsoft的并行扩展论坛上查看我的问题:http://social.msdn.microsoft.com/Forums/zh-CN/parallelextensions/thread/7c5c3051-133b-4814-9db0-fc0039b4f9d9在我的测试中,使用Parallel.For或Parallel.Invoke时,Trace.CorrelationManager.LogicalOperationStack似乎被破坏了。如果您在主线程中启动逻辑操作,则在委托中启动/停止逻辑操作。在我的测试中(参见上面两个链接之一),当DoLongRunningWork正在执行时,LogicalOperationStack应该始终有2个条目(如果我在使用各种技术启动DoLongRunningWork之前在主线程中启动逻辑操作)。因此,“损坏”是指LogicalOperationStack最终有超过2个条目。据我所知,这可能是因为Parallel.For和Parallel.Invoke将主线程用作执行DoLongRunningWork操作的“工作”线程之一。使用存储在CallContext.LogicalSetData中的堆栈来模仿LogicalOperationStack的行为(类似于通过CallContext.SetData存储的log4net的LogicalThreadContext.Stacks)会产生更糟糕的结果。如果我使用这样的堆栈来维护上下文,那么在几乎所有我在主线程中进行“逻辑操作”并且在DoLongRunningWork的每次迭代/执行中进行逻辑操作的情况下,它都会被销毁(即不是预期的条目数)代表。以上是C#学习教程:任务并行库中的任务如何影响ActivityID?如果所有分享的内容对你有用,需要进一步了解C#学习教程,希望大家多多关注。本文收集自网络,不代表立场。如涉及侵权,请点击右侧联系管理员删除。如需转载请注明出处: