【问题标题】:Xamarin Forms - Using Async SelectedIndexChanged in picker selectionXamarin Forms - 在选择器选择中使用 Async SelectedIndexChanged
【发布时间】:2019-03-02 10:27:26
【问题描述】:

我正在为 Android 和 iOS 在 Xamarin Forms 中实现一个应用程序,它与 WebAPI (.NET) 通信以从数据库中检索信息。 在某些时候,我有一个带有几个选择器的屏幕。假设在picker1中我们选择了一个国家;然后在picker2中我们选择一个城市。

当在picker1中选择一个国家时,它会引发一个SelectedIndexChanged事件(称为OnSelectedCountry),该事件调用WebAPI从该特定国家检索城市,然后通过picker2.ItemsSource将城市名称绑定到picker2。

这会导致性能马虎,并且看起来应用在执行 OnSelectedCountry 时卡住了,因为来自 picker1 的国家/地区列表直到 OnSelectedCountry 结束才关闭。 选择国家/地区后,如何立即关闭 picker1 中的列表项?或在其顶部显示“加载”图像。

我已经尝试过 this thread 的解决方案,但没有奏效。

代码片段(为简单起见省略了变量模型):

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)
{ ... }

【问题讨论】:

  • 在单独的任务中运行

标签: .net visual-studio xamarin xamarin.forms eventhandler


【解决方案1】:

我强烈建议阅读异步任务,因为如果您不熟悉它可能会非常棘手。如果你这样做,你将来会避免很多问题。

此代码使用不同的方法代替您的代码,因为我真的不想编写所有类等来测试代码,但希望 cmets 能帮助您将其集成到您的解决方案中。

更新:由于下面的解决方案不适用于 Laura 的方法,我怀疑 HTTP 调用没有使用 await,因此不是异步的。这是 UI 被阻塞的真正原因。

    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。
  • 哇,你是对的!我的错,我使用的是带有 GetResponse() 而不是 GetResponseAsyn() 的 HttpWebRequest。非常感谢,我学到了更多关于异步方法的知识,我明白了为什么没有必要将 EventHandler 标记为异步 :)
  • @LauraCP 不要从布鲁诺的答案中拿走任何东西,但我相信最好将其标记为正确答案,因为我们找到了问题的根本原因。
【解决方案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);
        }
    });

【讨论】:

  • 您可以这样做,但我宁愿不将事件处理程序标记为异步。也很可能从 Task 操作 picker2 将导致异常,因为只有 UI 线程可以修改 UI。您必须在 Device.BeginInvokeOnMainThread 方法委托中封装调用。
  • 我只需要按照@SKall 的建议将picker2.ItemsSourcepicker2.ItemsSource 包含在Device.BeginInvokeOnMainThread(() =&gt; { }); 中,并且效果很好!谢谢你们俩
  • @LauraCP 也看看我的解决方案。我没有将事件处理程序标记为异步是有原因的... :)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2021-06-29
  • 2018-03-10
  • 1970-01-01
  • 2019-08-05
  • 1970-01-01
  • 2019-02-05
  • 2015-06-01
相关资源
最近更新 更多