我在低级库中有一些代码需要知道调用链中是否有某个类位于它之上(我们正在从一个数据存储迁移到另一个数据存储,如果有人直接访问旧存储,我需要提醒不经过迁移对象)。通常我会检查堆栈跟踪,但有时候这个库中的方法是从Parallel.ForEach中调用的,并且由于大多数线程在线程池的上下文中运行,因此会中断堆栈跟踪。这是一个例子:
using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;
using System.Threading.Tasks;
namespace CallstackSpike
{
public class Program
{
public static void Main()
{
new MigrationUtil().DoWork(10);
}
}
internal class MigrationUtil
{
public void DoWork(int workCount)
{
new Fetcher().FetchStuff(workCount);
}
}
internal class Fetcher
{
private readonly Worker worker = new Worker();
public void FetchStuff(int workCount)
{
Console.WriteLine("Trying parallel foreach... ");
ConcurrentBag<bool> results = new ConcurrentBag<bool>();
Parallel.ForEach(Enumerable.Range(0, workCount),
new ParallelOptions { MaxDegreeOfParallelism = 4 },
idx =>
{
results.Add(worker.DoWork(idx));
});
bool result = results.Aggregate(true, (current, res) => current & res);
Console.WriteLine("result: " + result);
}
}
internal class Worker
{
private readonly Random random = new Random();
public bool DoWork(int idx)
{
Thread.Sleep(random.Next(100, 500));
string stackTrace = Environment.StackTrace;
Console.WriteLine("IDX = {0}\n{1}\n", idx, stackTrace);
return stackTrace.Contains("MigrationUtil");
}
}
}
有MaxDegreeOfParallelism
的4 75%的线程来自线程池,它们的堆栈跟踪如下所示:
at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
at System.Environment.get_StackTrace()
at CallstackSpike.Worker.DoWork(Int32 idx) in C:\Dev\Spikes\CallstackSpike\CallstackSpike\Program.cs:line 56
at CallstackSpike.Fetcher.<>c__DisplayClass1_0.<FetchStuff>b__0(Int32 idx) in C:\Dev\Spikes\CallstackSpike\CallstackSpike\Program.cs:line 41
at System.Threading.Tasks.Parallel.<>c__DisplayClass42_0`2.<PartitionerForEachWorker>b__1()
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
at System.Threading.Tasks.Task.<>c__DisplayClass176_0.<ExecuteSelfReplicating>b__0(Object )
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.Execute()
at System.Threading.Tasks.Task.ExecutionContextCallback(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
at System.Threading.Tasks.Task.System.Threading.IThreadPoolWorkItem.ExecuteWorkItem()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
剩余的25%的时间我可以看到完整的调用堆栈:
at System.Environment.GetStackTrace(Exception e, Boolean needFileInfo)
at System.Environment.get_StackTrace()
at CallstackSpike.Worker.DoWork(Int32 idx) in C:\Dev\Spikes\CallstackSpike\CallstackSpike\Program.cs:line 56
at CallstackSpike.Fetcher.<>c__DisplayClass1_0.<FetchStuff>b__0(Int32 idx) in C:\Dev\Spikes\CallstackSpike\CallstackSpike\Program.cs:line 41
at System.Threading.Tasks.Parallel.<>c__DisplayClass42_0`2.<PartitionerForEachWorker>b__1()
at System.Threading.Tasks.Task.InnerInvoke()
at System.Threading.Tasks.Task.InnerInvokeWithArg(Task childTask)
at System.Threading.Tasks.Task.<>c__DisplayClass176_0.<ExecuteSelfReplicating>b__0(Object )
at System.Threading.Tasks.Task.ExecuteSelfReplicating(Task root)
at System.Threading.Tasks.Task.Execute()
at System.Threading.Tasks.Task.ExecutionContextCallback(Object obj)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean preserveSyncCtx)
at System.Threading.Tasks.Task.ExecuteWithThreadLocal(Task& currentTaskSlot)
at System.Threading.Tasks.Task.ExecuteEntry(Boolean bPreventDoubleExecution)
at System.Threading.Tasks.ThreadPoolTaskScheduler.TryExecuteTaskInline(Task task, Boolean taskWasPreviouslyQueued)
at System.Threading.Tasks.TaskScheduler.TryRunInline(Task task, Boolean taskWasPreviouslyQueued)
at System.Threading.Tasks.Task.InternalRunSynchronously(TaskScheduler scheduler, Boolean waitForCompletion)
at System.Threading.Tasks.Task.RunSynchronously(TaskScheduler scheduler)
at System.Threading.Tasks.Parallel.PartitionerForEachWorker[TSource,TLocal](Partitioner`1 source, ParallelOptions parallelOptions, Action`1 simpleBody, Action`2 bodyWithState, Action`3 bodyWithStateAndIndex, Func`4 bodyWithStateAndLocal, Func`5 bodyWithEverything, Func`1 localInit, Action`1 localFinally)
at System.Threading.Tasks.Parallel.ForEachWorker[TSource,TLocal](IEnumerable`1 source, ParallelOptions parallelOptions, Action`1 body, Action`2 bodyWithState, Action`3 bodyWithStateAndIndex, Func`4 bodyWithStateAndLocal, Func`5 bodyWithEverything, Func`1 localInit, Action`1 localFinally)
at System.Threading.Tasks.Parallel.ForEach[TSource](IEnumerable`1 source, ParallelOptions parallelOptions, Action`1 body)
at CallstackSpike.Fetcher.FetchStuff(Int32 workCount) in C:\Dev\Spikes\CallstackSpike\CallstackSpike\Program.cs:line 39
at CallstackSpike.MigrationUtil.DoWork(Int32 workCount) in C:\Dev\Spikes\CallstackSpike\CallstackSpike\Program.cs:line 27
at CallstackSpike.Program.Main() in C:\Dev\Spikes\CallstackSpike\CallstackSpike\Program.cs:line 13
所以我的问题是,有没有办法查询当前线程是否是由于Parallel.ForEach,以及它是否来自之前的位置?某人必须知道,我只是不知道当前线程是否可以访问该信息。