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

时间:2017-12-21 13:34:51

标签: c# xamarin.android mono async-await

我有一个Xamarin.Android项目,TargetFramework = Android版本:8.0(奥利奥)。

我注意到以下问题:

  1. Thread.CurrentPrincipal设置为自定义主体(要特别小心以确保它可序列化!)
  2. 致电await Task.Run()以运行一些任务。
  3. Task内,如果它在不同的线程上执行,则不会设置Thread.CurrentPrincipal。
  4. 如果使用Task.Factory.StartNew()而不是Task.Run(),那么在后台线程上运行的Task确实正确设置了Thread.CurrentPrincipal。
  5. 换句话说,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. The Thread.CurrentPrinicpal
    2. Thread.CurrentThread.ManagedThreadId
    3. TaskScheduler.Default
    4. TaskScheduler.Current
    5. TaskScheduler.FromCurrentSynchronizationContext();
    6. 这是StartNew()之前的样子:

      enter image description here

      以下是StartNew()内的内容:

      enter image description here

      请注意,通过StartNew()调度的任务正在线程池线程上执行,而ShcnronisationContext TaskScheduler为null,因为此时没有当前的同步上下文。但是,请注意,线程的主体正确设置为" Daz"身份。

      在等待Task.Factory.StartNew()之后和调用Task.Run()之前,这是什么样子:

      enter image description here

      请注意,我们再次回到主线程,就像我们在调用StartNew()之前一样。

      然而,似乎SyncContext TaskScheduler已经改变(它的ID现在是3)。不确定这是否与此问题相关。

      现在这就是Task.Run()期间的情况:

      enter image description here

      注意: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

1 个答案:

答案 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,但这不会返回到调用上下文。它将安排线程继续运行在当时最佳的任何可能导致问题的情况下,如果您不知道。它总体上更有效,但要确保您的方法的其余部分不需要在相同的上下文中运行。

希望这有帮助。