我有一个Xamarin.Android项目,TargetFramework = Android版本:8.0(奥利奥)。
我注意到以下问题:
Thread.CurrentPrincipal
设置为自定义主体(要特别小心以确保它可序列化!)await Task.Run()
以运行一些任务。Task
内,如果它在不同的线程上执行,则不会设置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应用程序下进行调试时的一些屏幕截图,在不同的点上显示以下内容:
这是StartNew()之前的样子:
以下是StartNew()
内的内容:
请注意,通过StartNew()调度的任务正在线程池线程上执行,而ShcnronisationContext TaskScheduler为null,因为此时没有当前的同步上下文。但是,请注意,线程的主体正确设置为" Daz"身份。
在等待Task.Factory.StartNew()之后和调用Task.Run()之前,这是什么样子:
请注意,我们再次回到主线程,就像我们在调用StartNew()之前一样。
然而,似乎SyncContext TaskScheduler已经改变(它的ID现在是3)。不确定这是否与此问题相关。
现在这就是Task.Run()期间的情况:
注意:Thread.CurrentPrincipal.IdentityName已丢失(即因为Prinicpal没有像使用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
答案 0 :(得分:0)
修改开始:
我会留下余下的答案,虽然读这篇文章的人应该注意到这与问题无关,我不介意它悬而未决,但请不要将其标记下来。下面的沟通可能是相关的。
我仔细研究了这个问题并意识到了被问到的问题,并且可以诚实地同意我认为这可能是一个错误。我无法解释它,并且觉得向前迈进是件好事。我自己会关注这个问题以获得解决方案。
编辑结束:
我不完全确定我明白你在做什么。使用断点可能并将鼠标悬停在变量上?将这些变量放在两个任务之外,然后在它们运行后查看它们。
但这是我认为你正在寻找的答案。输入Foo时,它只是一个具有Task返回类型的异步方法。是的,这意味着我们可以在调用时等待它,但在实际运行新线程之前没有任何东西等待。因此,在Foo到达await Foo()
之前,您将保持在await Task.Factory.StartNew
的任何上下文调用。
调用Task.Factory.StartNew
从线程池中获取一个线程并在该线程上执行传递的操作。完成后,计划在调用它的相同上下文中返回Foo
。现在,上下文再次等待await Task.Run
,它与await Task.Factory.StartNew
完全相同。你将从线程池中获得一个线程,并在那里运行工作等。
这两个调用之间只有一个区别,那就是传递给TaskFactory的TaskCreationOptions.LongRunning
参数......老实说工厂所做的并不是具体或在所有环境中都是一样的,但它是让工厂出厂的知道你的线程将在更长的时间内存活。我相信它在Windows中从一个不同的线程池中拉出线程意味着运行更长时间,但我不完全确定。无论哪种方式;你仍然在每个调用的新线程上,而不是同一个线程,并且你回到了同一个线程中。
此外,您可以在.ConfigureAwait(false);
次调用结束时使用await
,但这不会返回到调用上下文。它将安排线程继续运行在当时最佳的任何可能导致问题的情况下,如果您不知道。它总体上更有效,但要确保您的方法的其余部分不需要在相同的上下文中运行。
希望这有帮助。