如何在Xamarin Android活动回调中使用await

时间:2017-02-02 17:21:17

标签: c# android asynchronous xamarin xamarin.android

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

所以我有一个带有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
        });
    }

从某种意义上说,它可以正确执行。但是,活动显示为空白屏幕,即使文本视图仅在一定延迟后呈现。因此看起来任务实际上并非异步运行。在“await”调用上设置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应该能够作为单独的线程在这里运行?

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

3 个答案:

答案 0 :(得分:16)

通过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

答案 1 :(得分:1)

为了补充@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方法退出,而数据仍在加载。

答案 2 :(得分:0)

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