Xamarin Android - Task.Run vs Task.Factory.StartNew和Thread.CurrentPrincipal

书到用时方恨少,事非经过不知难。这篇文章主要讲述Xamarin Android - Task.Run vs Task.Factory.StartNew和Thread.CurrentPrincipal相关的知识,希望能为你提供帮助。
我有一个Xamarin.android项目,TargetFramework = Android版本:8.0(奥利奥)。
我注意到以下问题:

  1. Thread.CurrentPrincipal设置为Custom主体(要特别小心以确保它是可序列化的!)
  2. 调用await Task.Run()来运行一些任务。
  3. Task中,如果它在不同的Thread上执行,则不设置Thread.CurrentPrincipal。
  4. 如果您使用Task.Factory.StartNew()而不是Task.Run(),那么在后台线程上运行的任务确实正确设置了Thread.CurrentPrincipal。
换句话说,Task.Factory.StartNew似乎将CurrentPrinciple传递给新线程,而Task.Run不会
此外,如果您在NET 4.7上重复此测试,Thread.CurrentPrincipal在两种情况下都能正确流动,因此只有在Mono / Xamarin.Android上运行时才会出现这种行为差异。
这是一个测试用例:
[Fact] public async Task LoginService_WhenUserLogsIn_PrincipalFlowsToAsyncTask() {var mockIdentity = new MockIdentity(true); var mockPrincipal = new MockPrincipal(mockIdentity); Thread.CurrentPrincipal = mockPrincipal ; await Task.Factory.StartNew(async () => { var newThreadId = Thread.CurrentThread.ManagedThreadId; // on different thread. Assert.True(Thread.CurrentPrincipal.Identity.IsAuthenticated); Assert.Equal(mockPrincipal, Thread.CurrentPrincipal); await Task.Factory.StartNew(() => { // still works even when nesting.. newThreadId = Thread.CurrentThread.ManagedThreadId; Assert.True(Thread.CurrentPrincipal.Identity.IsAuthenticated); Assert.Equal(mockPrincipal, Thread.CurrentPrincipal); }, TaskCreationOptions.LongRunning); }, TaskCreationOptions.LongRunning); await Task.Run(() => { // Following works on NET4.7 and fails under Xamarin.Android. var newThreadId = Thread.CurrentThread.ManagedThreadId; Assert.True(Thread.CurrentPrincipal.Identity.IsAuthenticated); Assert.Equal(mockPrincipal, Thread.CurrentPrincipal); }); }

以下是在Xamarin.Android应用程序下进行调试时的一些屏幕截图,在不同点显示以下内容:
  1. Thread.CurrentPrincipal
  2. Thread.CurrentThread.ManagedThreadId
  3. TaskScheduler.Default
  4. TaskScheduler.Current
  5. TaskScheduler.FromCurrentSynchronizationContext();
这是StartNew()之前的样子:
Xamarin Android - Task.Run vs Task.Factory.StartNew和Thread.CurrentPrincipal

文章图片

以下是StartNew()内部的内容:
Xamarin Android - Task.Run vs Task.Factory.StartNew和Thread.CurrentPrincipal

文章图片

请注意,通过StartNew()调度的任务正在线程池线程上执行,而ShcnronisationContext TaskScheduler为null,因为此时没有当前的同步上下文。但是,还要注意线程的主体正确设置为“Daz”身份。
在等待Task.Factory.StartNew()之后和调用Task.Run()之前,这是什么样子:
Xamarin Android - Task.Run vs Task.Factory.StartNew和Thread.CurrentPrincipal

文章图片

请注意,我们再次回到主线程,就像我们在调用StartNew()之前一样。
但是看起来SyncContext TaskScheduler已经改变了(它的ID现在是3)。不确定这是否与此问题相关。
现在这是Task.Run()期间的情况:
Xamarin Android - Task.Run vs Task.Factory.StartNew和Thread.CurrentPrincipal

文章图片

注意:Thread.CurrentPrincipal.Identity名称已丢失(因为Principal尚未像使用StartNew()那样流向此线程。
我们仍然在后台线程,就像我们使用StartNew()一样。这个线程上没有SynchronisationContext,这是我们对后台线程的期望,并且在StartNew()中是相同的,所以没有区别。默认和当前任务调度程序看起来相同(ID = 1),因此也没有区别。
我能看到的唯一区别是,校长没有流入线程。
是什么原因?这是一个错误吗?我认为在使用Task.Run()时,Thread.CurrentPrinicpal应始终使用ExecutionContext。
我还在这里向Github提出了一个问题:https://github.com/xamarin/xamarin-android/issues/1130
更新:看起来这已被承认为一个错误,希望它将修复:https://github.com/mono/mono/pull/6326/files
答案编辑开始:
我会留下余下的答案,虽然任何阅读此内容的人都应该注意到这与问题无关,我不介意它悬而未决,但请不要将其标记下来。下面的沟通可能是相关的。
我仔细研究了这个问题并意识到问题是什么,并且可以诚实地同意我认为这可能是一个错误。我无法解释它,并且觉得向前迈进是件好事。我将继续关注这个问题以获得解决方案。
【Xamarin Android - Task.Run vs Task.Factory.StartNew和Thread.CurrentPrincipal】编辑结束:
我不完全确定我明白你在做什么。使用断点可能并将鼠标悬停在变量上?将这些变量放在两个任务之外,然后在它们运行后查看它们。
但这是我认为你正在寻找的答案。输入Foo时,它只是一个具有Task返回类型的异步方法。是的,这意味着我们可以在调用时等待它,但在实际运行新线程之前没有任何东西等待。因此,你保持在任何上下文调用await Foo(),直到Foo到达await Task.Factory.StartNew
调用Task.Factory.StartNew从线程池中获取一个线程,并在该线程上执行传递的操作。一旦完成,它将被安排在调用它的相同上下文中返回Foo。现在,上下文再次等待await Task.Run,它与await Task.Factory.StartNew完全相同。你将从线程池中获得一个线程,并在那里运行工作等。
两个调用只有一个区别,那就是传递给TaskFactory的TaskCreationOptions.LongRunning参数......老实说,工厂所做的事情并非在所有环境中具体或相同,但是它让工厂知道你的线程将会存在更长的一段时间。我相信它在Windows中从一个不同的线程池中拉出线程意味着运行更长时间,但我不完全确定。无论哪种方式; 你仍然在每个调用的新线程上,而不是同一个线程,并且你又回到了同一个线程。
此外,您可以在.ConfigureAwait(false); 调用结束时使用await,但这不会返回到调用上下文。它将安排线程继续运行在当时最佳的任何可能导致问题的情况下,如果您不知道。它总体上更有效,但要确保您的方法的其余部分不需要在相同的上下文中运行。
希望这可以帮助。

    推荐阅读