书到用时方恨少,事非经过不知难。这篇文章主要讲述Xamarin Android - Task.Run vs Task.Factory.StartNew和Thread.CurrentPrincipal相关的知识,希望能为你提供帮助。
我有一个Xamarin.android项目,TargetFramework = Android版本:8.0(奥利奥)。
我注意到以下问题:
- 将
Thread.CurrentPrincipal
设置为Custom主体(要特别小心以确保它是可序列化的!) - 调用
await Task.Run()
来运行一些任务。 - 在
Task
中,如果它在不同的Thread上执行,则不设置Thread.CurrentPrincipal。 - 如果您使用Task.Factory.StartNew()而不是Task.Run(),那么在后台线程上运行的任务确实正确设置了Thread.CurrentPrincipal。
此外,如果您在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应用程序下进行调试时的一些屏幕截图,在不同点显示以下内容:
- Thread.CurrentPrincipal
- Thread.CurrentThread.ManagedThreadId
- TaskScheduler.Default
- TaskScheduler.Current
- TaskScheduler.FromCurrentSynchronizationContext();
文章图片
以下是
StartNew()
内部的内容:文章图片
请注意,通过StartNew()调度的任务正在线程池线程上执行,而ShcnronisationContext TaskScheduler为null,因为此时没有当前的同步上下文。但是,还要注意线程的主体正确设置为“Daz”身份。
在等待Task.Factory.StartNew()之后和调用Task.Run()之前,这是什么样子:
文章图片
请注意,我们再次回到主线程,就像我们在调用StartNew()之前一样。
但是看起来SyncContext TaskScheduler已经改变了(它的ID现在是3)。不确定这是否与此问题相关。
现在这是Task.Run()期间的情况:
文章图片
注意: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
,但这不会返回到调用上下文。它将安排线程继续运行在当时最佳的任何可能导致问题的情况下,如果您不知道。它总体上更有效,但要确保您的方法的其余部分不需要在相同的上下文中运行。希望这可以帮助。
推荐阅读
- 为什么webview头键在android中以小写形式转换()
- 在VS 2017 xamarin android项目中没有击中断点
- 同步/阻止Application.Invoke()for GTK#
- 如何在Cordova中检索应用程序版本,应用程序名称,程序包名称,版本代码和版本号
- 5个最佳网页加载进度条javascript插件
- 5+最佳的jquery和javascript datepicker插件
- 5个最佳Cookie政策横幅javascript插件
- 5+最佳代码语法高亮javascript插件
- 5个人脸跟踪和识别相关的最佳javascript库