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

11

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

我有一个包含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应该能够在这里作为单独的线程运行?
附注:删除设置适配器数据的代码不会影响此行为 - 屏幕渲染仍然会有延迟。所以我在这里没有展示那段代码。
3个回答

20

通过在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时,等待的操作在不同的任务中启动,但该方法立即退出并让线程继续)。但这是一个指向问题根源的优秀答案。简而言之,不要在Activity回调中使用await。 - Dennis K
@DennisK 由于你使用了 Task.Run,所以等待的操作正在另一个线程上运行,但是等待的方法和线程已被挂起,直到等待的操作返回。 - SushiHangover
我知道你是对的,但我还是无法理解。这就是让我困惑的地方: https://msdn.microsoft.com/en-us/library/mt674882.aspx。 "在 async 方法中的 await 表达式不会阻塞当前线程,而等待任务正在运行。相反,表达式将方法的其余部分作为后续操作进行注册,并将控件返回给异步方法的调用者。"。它明确表示“await 不会阻塞当前线程”,因此我期望 OnCreate 将控制返回给调用者。 - Dennis K
我认为我找到了真正的问题,它位于你指出的路径深处。我会将你的答案保留为已接受,但为了完整性,我会添加我的答案。(顺便说一下,在OnResume中放置“UI线程/消息循环器未被阻塞”的消息比在OnCreate末尾更好,以便正确指示未阻塞。即使OnCreate被暂停,它也会被调用) - Dennis K

1

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

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

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

另一种途径,在某些情况下可能更合适,就是等待在该方法内部创建的Task:

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,因为您从工作线程修改了视图。 - JonZarate

-1

抱歉,您能详细说明一下吗?哪些内容没有在UI线程上运行?这里的重点是不要在UI线程上运行长时间任务。问题在于UI线程似乎仍然被阻塞了。 - Dennis K

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接