Я реализую приложение в 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)
{ ... }
Вы должны запустить его в другой задаче, чтобы он не завис.
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.
Мне просто нужно было обернуть строки picker2.ItemsSource
и picker2.ItemsSource
в Device.BeginInvokeOnMainThread(() => { });
, как предложил @SKall, и все заработало! Спасибо вам обоим
@LauraCP тоже взгляните на мое решение. Есть причина, по которой я не пометил обработчик событий async ... :)
Я настоятельно рекомендую прочитать об асинхронной задаче, поскольку она может быть очень сложной, если вы с ней не знакомы. Если вы это сделаете, вы избежите многих проблем в будущем.
В этом коде вместо вашего кода используются разные методы, так как я действительно не хотел писать все классы и т. д. Для тестирования кода, но, надеюсь, комментарии помогут вам интегрировать это в ваше решение.
ОБНОВИТЬ: поскольку приведенное ниже решение не работало с методом Лауры, я подозревал, что 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( ...
Странно, ведь он работал без зависания экрана. Что ж, если вам подходит другое решение, вы можете двигаться дальше. Я все же рекомендую посмотреть, как работает асинхронная задача, так как это ускорит вашу разработку в будущем.
Вы уверены, что реализация RestService.GetDataAsync является асинхронной? Посмотрите, есть ли предупреждение CS1998 для метода.
Ух ты прав !! По моей вине, я использовал HttpWebRequest с GetResponse () вместо GetResponseAsyn (). Большое вам спасибо, я узнал больше об асинхронных методах, и я должен понять, почему нет необходимости отмечать EventHandler как async :)
@LauraCP, чтобы ничего не убирать из ответа Бруно, но я считаю, что было бы хорошо отметить это как правильный ответ, поскольку мы нашли основную причину проблемы.
запустить его в отдельной задаче