it-roy-ru.com

Как использовать await в обратных вызовах 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
        });
    }

Это работает, в том смысле, что оно выполняется без ошибок. Однако действие отображается как пустой экран, и даже текстовое представление отображается только после некоторой задержки. Таким образом, похоже, что задача на самом деле не выполняется асинхронно. Установка 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); 

разблокировать поток? Почему мы должны принудительно задерживать начало асинхронной операции, чтобы пользовательский интерфейс завершил рендеринг, хотя (в соответствии с этим http://www.wintellect.com/devcenter/paulballard/tasks-are-still-not-threads -and-async-is-not -rallel ) Предполагается, что SetupData может работать как отдельный поток?

постскриптум удаление кода, устанавливающего данные на адаптере, не влияет на это поведение - перед рендерингом экрана все еще есть задержка. Так что я не показываю этот код здесь.

6
Dennis K

Awaiting в 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
16
SushiHangover

Чтобы дополнить ответ от @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 работать должным образом - метод OnCreate завершается, пока данные все еще загружаются.

0
Dennis K

Поскольку он не работает в потоке пользовательского интерфейса, это может помочь вам получить более четкое представление Что такое Android UiThread (поток пользовательского интерфейса)

0
Mike