Bud*_*rot 5 c# asynchronous task-parallel-library async-await .net-core
在应用程序中,由于 AsyncLocal 的错误/意外值,我遇到了奇怪的行为:尽管我抑制了执行上下文的流程,但 AsyncLocal.Value 属性有时不会在新生成的任务的执行范围内重置。
\n下面我创建了一个最小的可重现示例来演示该问题:
\nprivate static readonly AsyncLocal<object> AsyncLocal = new AsyncLocal<object>();\n[TestMethod]\npublic void Test()\n{\n Trace.WriteLine(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);\n var mainTask = Task.Factory.StartNew(() =>\n {\n AsyncLocal.Value = "1";\n Task anotherTask;\n using (ExecutionContext.SuppressFlow())\n {\n anotherTask = Task.Run(() =>\n {\n Trace.WriteLine(AsyncLocal.Value); // "1" <- ???\n Assert.IsNull(AsyncLocal.Value); // BOOM - FAILS\n AsyncLocal.Value = "2";\n });\n }\n\n Task.WaitAll(anotherTask);\n });\n\n mainTask.Wait(500000, CancellationToken.None);\n}\nRun Code Online (Sandbox Code Playgroud)\n在十分之九的运行中(在我的电脑上),测试方法的结果是:
\n.NET 6.0.2\n"1"\nRun Code Online (Sandbox Code Playgroud)\n-> 测试失败
\n正如您所看到的,测试失败,因为在Task.Run之前的值仍然存在于AsyncLocal.Value(Message:)中执行的操作中1。
我的具体问题是:
\n这是对此行为的正确解释吗?另外,为什么它有时可以完美地工作(在我的机器上大约有十分之一)?
\nprivate static readonly AsyncLocal<object> AsyncLocal = new AsyncLocal<object>();\n[TestMethod]\npublic void Test()\n{\n Trace.WriteLine(System.Runtime.InteropServices.RuntimeInformation.FrameworkDescription);\n var mainTask = Task.Factory.StartNew(() =>\n {\n AsyncLocal.Value = "1";\n Task anotherTask;\n using (ExecutionContext.SuppressFlow())\n {\n var wrapper = () =>\n {\n Trace.WriteLine(AsyncLocal.Value);\n Assert.IsNull(AsyncLocal.Value); \n AsyncLocal.Value = "2";\n return Task.CompletedTask;\n };\n\n anotherTask = Task.Run(async () => await wrapper());\n }\n\n Task.WaitAll(anotherTask);\n });\n\n mainTask.Wait(500000, CancellationToken.None);\n}\n\nRun Code Online (Sandbox Code Playgroud)\n这似乎解决了问题(它在我的机器上始终有效),但我想确保这是解决此问题的正确方法。
\n提前谢谢了
\n为什么会出现这种情况?我怀疑发生这种情况是因为 Task.Run 可能使用当前线程来执行工作负载。
我怀疑发生这种情况是因为Task.WaitAll将使用当前线程来内联执行任务。
具体来说,Task.WaitAll调用Task.WaitAllCore,它将尝试通过调用 来内联运行它Task.WrappedTryRunInline。我假设自始至终都使用默认的任务调度程序。在这种情况下,这将调用,如果委托已经被调用,TaskScheduler.TryRunInline它将返回false。因此,如果任务已经开始在线程池线程上运行,这将返回到WaitAllCore,这将只执行正常的等待,并且您的代码将按预期工作(十分之一)。
如果线程池线程尚未获取它(十分之九),则将TaskScheduler.TryRunInline调用TaskScheduler.TryExecuteTaskInline,其默认实现将调用Task.ExecuteEntryUnsafe,其调用Task.ExecuteWithThreadLocal。Task.ExecuteWithThreadLocal具有应用ExecutionContextif one 被捕获的逻辑。假设没有捕获任何内容,则直接调用任务的委托。
所以,看起来每一步都是合乎逻辑的。从技术上讲,ExecutionContext.SuppressFlow意思是“不捕获ExecutionContext”,而这就是正在发生的事情。它并不意味着“清除” ExecutionContext。有时,任务在线程池线程上运行(没有捕获ExecutionContext),并且WaitAll只会等待它完成。其他时候,任务将由WaitAll线程池线程而不是内联执行,在这种情况下,ExecutionContext不会清除(并且从技术上讲也不会捕获)。
您可以通过捕获当前线程 IDwrapper并将其与执行Task.WaitAll. 我希望对于异步本地值(意外地)继承的运行来说,它们将是相同的线程,并且对于异步本地值按预期工作的运行来说,它们将是不同的线程。