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

问题描述 投票:2回答:1

我有一个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()之前的样子:

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.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

c# xamarin.android mono async-await
1个回答
0
投票

编辑开始:

我会留下余下的答案,虽然任何阅读此内容的人都应该注意到这与问题无关,我不介意它悬而未决,但请不要将其标记下来。下面的沟通可能是相关的。

我仔细研究了这个问题并意识到问题是什么,并且可以诚实地同意我认为这可能是一个错误。我无法解释它,并且觉得向前迈进是件好事。我将继续关注这个问题以获得解决方案。

编辑结束:

我不完全确定我明白你在做什么。使用断点可能并将鼠标悬停在变量上?将这些变量放在两个任务之外,然后在它们运行后查看它们。

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

希望这可以帮助。

© www.soinside.com 2019 - 2024. All rights reserved.