【问题标题】:How to use await in Xamarin Android activity callbacks如何在 Xamarin Android 活动回调中使用 await
【发布时间】:2017-02-02 17:21:16
【问题描述】:

标题可能有点误导,我的问题更多是关于为什么它以这种奇怪的方式工作。

所以我有一个布局,其中包含一个 TextView 和一个 ListView。我有一个长时间运行的异步方法,可以准备要在列表中显示的数据。所以初始代码是这样的:

    protected async override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        SetContentView(Resource.Layout.MyView);
        await SetupData();
    }

    private async Task SetupData(){
        Task.Run(async () => {
            var data = await new SlowDataLoader().LoadDataAsync();
            // For simplicity setting data on the adapter is omitted here
        });
    }

它有效,从某种意义上说,它执行时没有错误。但是,活动显示为空白屏幕,甚至文本视图也仅在一定延迟后呈现。所以看起来任务实际上并没有异步运行。在两个“等待”调用上设置 ConfigureAwait(false) 没有帮助。将 SetupData() 调用移至 OnPostCreate、OnResume 和 OnPostResume 没有任何效果。当数据到达时,唯一使 TextView 立即出现并稍后呈现列表的是:

    protected override void OnCreate(Bundle savedInstanceState)
    {
        base.OnCreate(savedInstanceState);
        SetContentView(Resource.Layout.MyView);
        new Handler().PostDelayed(async ()=>{
            await SetupData();
        }, 100);
    }

所以问题是,为什么不

await SetupData().ConfigureAwait(false); 

解除阻塞?为什么我们必须强制延迟异步操作的开始以让 UI 完成渲染,即使(根据这个 http://www.wintellect.com/devcenter/paulballard/tasks-are-still-not-threads-and-async-is-not-parallel)SetupData 应该能够在这里作为单独的线程运行?

附言删除在适配器上设置数据的代码不会影响此行为 - 在呈现屏幕之前仍有延迟。所以我不在这里显示该代码。

【问题讨论】:

    标签: c# android asynchronous xamarin xamarin.android


    【解决方案1】:

    通过在 UI Looper 中等待,您将在 SetupData 方法运行时阻止线程上的进一步代码执行。

    非阻塞示例:

        protected override void OnCreate(Bundle savedInstanceState)
        {
            base.OnCreate(savedInstanceState);
    
            SetContentView(Resource.Layout.Main);
            Task.Run(() => SetupData());
            Console.WriteLine("UI Thread / Message Looper is not blocked");
        }
    
        void SetupData()
        {
            Task.Run(async () =>
            {
                Console.WriteLine($"Are we on the UI thread? {Looper.MainLooper.Thread == Looper.MyLooper()?.Thread}");
                // Simulate a long running task
                await Task.Delay(TimeSpan.FromSeconds(10));
                Console.WriteLine("Done fetching/calculating data");
                RunOnUiThread(() =>
                {
                    // Update the data fetched/calculated on the UI thread;
                    Console.WriteLine($"Are we on the UI thread? {Looper.MainLooper.Thread == Looper.MyLooper().Thread}");
                });
            }).Wait();
            Console.WriteLine("Done w/ SetupData");
        }
    

    输出:

    UI Thread / Message Looper is not blocked
    Are we on the UI thread? False
    Done fetching/calculating data
    Are we on the UI thread? True
    Done w/ SetupData
    

    【讨论】:

    • 就是这样!实际上,只需在原始代码中从 OnCreate 中删除 await 即可修复它(即无需从任务中调用 SetupData)。我仍然需要理解为什么会发生这种情况(我认为当调用 await 时,等待的操作是在不同的任务中启动的,但是该方法立即退出并让线程继续)。但这是一个很好的答案,指出了问题的根源。 TL;DR = 不要在 Activity 回调中等待。
    • @DennisK awaited 操作在不同的线程上运行,因为您使用了Task.Run,但是方法,因此线程,即 awaiting 已暂停,直到 awaited 操作返回。
    • 我确定你是对的,但我仍然无法理解它。这让我感到困惑:msdn.microsoft.com/en-us/library/mt674882.aspx。 “异步方法中的等待表达式在等待的任务正在运行时不会阻塞当前线程。相反,该表达式将方法的其余部分注册为延续并将控制权返回给异步方法的调用者。”。它特别说“等待不会阻塞当前线程”,所以我期待 OnCreate 将控制权返回给调用者..
    • 我想我发现了真正的问题,它位于您突出显示的路径的更深处。我会保留您的答案,但为了完整起见,请添加我自己的答案。 (顺便说一句,最好将“UI Thread / Message Looper is not blocked”消息放入 OnResume 而不是放在 OnCreate 的末尾以正确指示解除阻塞。即使 OnCreate 被挂起,它也会被调用)
    【解决方案2】:

    为了补充@SushiHangover 的答案,除了@SushiHangover 建议的答案之外,我添加了我自己的答案以指出实际的错误并列出可能的解决方案。

    考虑本页最底部的示例https://msdn.microsoft.com/en-us/library/hh156528.aspx

    原始代码(以及我尝试过的所有其他变体)中的真正问题是,即使 SetupData 被声明为异步方法,它实际上是同步运行的。因此,当 OnCreate 等待同步方法时,它是阻塞的(正是他们在上面的示例中演示的内容)。可以通过多种方式纠正此问题。首先,正如 SushiHangover 建议的那样,不要等待这个方法,因为它是同步的,所以就这样调用它(也可以删除 async 关键字并从中返回 void)。

    在某些情况下可能更适合的另一种方法是等待在该方法中创建的任务:

    private async Task SetupData(){
        await Task.Run(async () => {
            var data = await new SlowDataLoader().LoadDataAsync();
            // For simplicity setting data on the adapter is omitted here
        });
    }
    

    或者,通过返回任务更改此方法以符合异步方法要求:

    private Task SetupData(){
        return Task.Run(async () => {
            var data = await new SlowDataLoader().LoadDataAsync();
            // For simplicity setting data on the adapter is omitted here
        });
    }
    

    这两项更改都允许 OnCreate 中的 await 按预期工作 - OnCreate 方法退出,而数据仍在加载。

    【讨论】:

    • 这真的有效吗?如果您操纵 UI(例如您提到的适配器),我会期望 CalledFromWrongThreadException,因为您从工作线程修改 Views。
    【解决方案3】:

    因为它不在 UI Thread 上运行,这可以帮助您获得更清晰的视图What is the Android UiThread (UI thread)

    【讨论】:

    • 抱歉,您能详细说明一下吗? UI线程上没有运行什么?这里的重点是不要在 UI 线程上运行长任务。问题是 UI 线程似乎仍然被阻塞。
    猜你喜欢
    • 2022-01-05
    • 1970-01-01
    • 2018-11-19
    • 2013-01-25
    • 1970-01-01
    • 1970-01-01
    • 2017-11-04
    • 1970-01-01
    • 2016-08-21
    相关资源
    最近更新 更多