Xamarin Forms - Использование Async SelectedIndexChanged в выборе средства выбора

Я реализую приложение в Xamarin Forms для Android и iOS, которое взаимодействует с WebAPI (.NET) для получения информации из базы данных. В какой-то момент у меня появляется экран с несколькими сборщиками. Скажем, в picker1 мы выбираем страну; далее в picker2 выбираем город.

Когда страна выбирается в picker1, возникает событие SelectedIndexChanged (называемое OnSelectedCountry), которое вызывает WebAPI для получения городов из этой конкретной страны, а затем связывает названия городов с picker2 с помощью picker2.ItemsSource.

Это приводит к неаккуратной работе и выглядит так, как будто приложение зависает при выполнении OnSelectedCountry, потому что список стран из picker1 не закрывается до тех пор, пока OnSelectedCountry не закончится. Как я мог закрыть элементы списка из picker1 сразу после выбора страны? Или покажите поверх него «загружающееся» изображение.

Я уже пробовал решения от эта ветка, но они не работали.

Пример кода (модели переменных для простоты опущены):

XAML

<Picker x:Name = "picker1" Title = "Select a country" ItemDisplayBinding = "{Binding name}" SelectedIndexChanged = "OnSelectedCountry" IsEnabled = "False"/>
<Picker x:Name = "picker2" Title = "Select a city" IsEnabled = "False" ItemDisplayBinding = "{Binding name}"/>

CS

 ...

 private ObservableCollection<City> _replyCities;

 ...

 async void OnSelectedCountry(object sender, EventArgs e)
 { 
      Country selectedCountry = (Country)picker1.SelectedItem;
      string decodedJson = await RestService.GetDataAsync(selectedCountry.name);
      Reply reply = Newtonsoft.Json.JsonConvert.DeserializeObject<Reply>(decodedJsonInput);

      _replyCities = new ObservableCollection<City>(reply.Cities);
      picker2.ItemsSource = _replyCities;
      picker2.IsEnabled = true;
 }

Связь с WebAPI выполняется в классе RestService, например:

public static async Task<String> GetDataAsync(string queryInput)
{ ... }

запустить его в отдельной задаче

Jason 26.09.2018 17:23
Стоит ли изучать PHP в 2023-2024 годах?
Стоит ли изучать PHP в 2023-2024 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
1
1
3 443
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы должны запустить его в другой задаче, чтобы он не завис.

Task.Run(async () => {
        try
        {
            string decodedJson = await RestService.GetDataAsync(selectedCountry.name);
             Reply reply = Newtonsoft.Json.JsonConvert.DeserializeObject<Reply>(decodedJsonInput);

      _replyCities = new ObservableCollection<City>(reply.Cities);
      picker2.ItemsSource = _replyCities;
      picker2.IsEnabled = true;


        }
        catch (System.OperationCanceledException ex)
        {
            Console.WriteLine($"Text load cancelled: {ex.Message}");
        }
        catch (Exception ex)
        {
            Console.WriteLine(ex.Message);
        }
    });

Вы могли бы это сделать, но я бы предпочел не отмечать обработчик событий async. Также, скорее всего, манипулирование picker2 из задачи приведет к исключению, поскольку только поток пользовательского интерфейса может изменять пользовательский интерфейс. Вам нужно будет завершить вызовы внутри делегата метода Device.BeginInvokeOnMainThread.

SKall 27.09.2018 04:52

Мне просто нужно было обернуть строки picker2.ItemsSource и picker2.ItemsSource в Device.BeginInvokeOnMainThread(() => { });, как предложил @SKall, и все заработало! Спасибо вам обоим

Laura C 27.09.2018 11:06

@LauraCP тоже взгляните на мое решение. Есть причина, по которой я не пометил обработчик событий async ... :)

SKall 27.09.2018 20:48
Ответ принят как подходящий

Я настоятельно рекомендую прочитать об асинхронной задаче, поскольку она может быть очень сложной, если вы с ней не знакомы. Если вы это сделаете, вы избежите многих проблем в будущем.

В этом коде вместо вашего кода используются разные методы, так как я действительно не хотел писать все классы и т. д. Для тестирования кода, но, надеюсь, комментарии помогут вам интегрировать это в ваше решение.

ОБНОВИТЬ: поскольку приведенное ниже решение не работало с методом Лауры, я подозревал, что HTTP-вызов не использует ожидание и, следовательно, не является асинхронным. Это была истинная причина блокировки пользовательского интерфейса.

    private void Picker1_OnSelectedIndexChanged(object sender, EventArgs e)
    {
        // add code here to display wait icon if you want


        // replace with RestService.GetDataAsync
        Task.Delay(5000)
            .ContinueWith(t =>
        {
            // in the case there is an error making the REST call
            if (t.IsFaulted)
            {
                // handle error
                return;
            }

            // t.Result will have the REST response so replace this code with yours
            var cities = new ObservableCollection<string>(new[] {"1", "2"});

            // Since you're running a new task it is important to make sure all UI 
            // changes are ran in the main UI thread. This will make sure of that.
            Device.BeginInvokeOnMainThread(() =>
            {
                Picker2.ItemsSource = cities;
                Picker2.IsEnabled = true;
                // if you displayed wait icon make sure it is disabled here now that the function has completed

            });
        });
    }

`` ''

Привет, я попытался интегрировать это решение, но id не работал должным образом. Он работает как раньше, и экран все равно зависает. Заменяю как RestService.GetDataAsync(selectedCountry.name).ContinueWith( ...

Laura C 27.09.2018 10:54

Странно, ведь он работал без зависания экрана. Что ж, если вам подходит другое решение, вы можете двигаться дальше. Я все же рекомендую посмотреть, как работает асинхронная задача, так как это ускорит вашу разработку в будущем.

SKall 27.09.2018 20:51

Вы уверены, что реализация RestService.GetDataAsync является асинхронной? Посмотрите, есть ли предупреждение CS1998 для метода.

SKall 27.09.2018 21:37

Ух ты прав !! По моей вине, я использовал HttpWebRequest с GetResponse () вместо GetResponseAsyn (). Большое вам спасибо, я узнал больше об асинхронных методах, и я должен понять, почему нет необходимости отмечать EventHandler как async :)

Laura C 28.09.2018 10:11

@LauraCP, чтобы ничего не убирать из ответа Бруно, но я считаю, что было бы хорошо отметить это как правильный ответ, поскольку мы нашли основную причину проблемы.

SKall 02.10.2018 15:03

Другие вопросы по теме