【问题标题】:C# (Xamarin) "async void" in framework override框架覆盖中的 C#(Xamarin)“async void”
【发布时间】:2017-03-09 18:17:56
【问题描述】:

我有一个从远程服务器检索信息并将该信息显示给用户的屏幕。我希望在显示屏幕时更新此信息(无需额外的用户交互)。

public partial class MyPage : ContentPage
{
    protected override async void OnAppearing()
    {
        base.OnAppearing();
        try
        {
            MyLabel.Text = (await GoGetTheData ()).Stuff;
        }
        catch (Exception e)
        {
            MyLabel.Text = "Narg";
        }
    }
}

这可以接受吗?我已经看到了所有关于“async void”的弊端的讨论,以及它应该如何只用于事件处理程序,而这并不是一个事件处理程序。

在这种情况下,框架是否保证正常,因为我 (1) 在获取所有异步之前调用了 base.OnAppearing(),并且 (2) 捕获了我的异常?

【问题讨论】:

  • 这个方法是怎么调用的?
  • 这是可以接受的,因为这些覆盖与事件处理程序没有太大区别:它们的存在是为了通知派生类的实例有关某些事件。但是,您应该跟踪待处理的任务,例如 GoGetTheData。如果在前一个任务完成之前再次调用OnAppearing 怎么办?如果您愿意,可以将其视为异步重入。
  • 我更新了我的代码以表明这是对最初在 Xamarin.Forms.Page 中定义的“OnAppearing()”的覆盖。我知道在人们回答后更新代码通常是不好的形式,但我相信在这种情况下,我更新的代码绝不会使任何答案或其他讨论无效。

标签: c# asynchronous xamarin async-await


【解决方案1】:

这是可以接受的。添加了对这些框架覆盖的异步支持是有原因的。一般来说,“async void”是有弊端的,但这种情况并不适用。

但请注意,您应该确保您的 OnAppearing(Forms)OnCreate(Android)ViewDidLoad(iOS) 方法尽快返回,以便您的用户获得愉快的体验。

事实上,您会在大量表单示例中看到这种确切的语法:https://github.com/xamarin/xamarin-forms-samples/search?utf8=%E2%9C%93&q=async+void+OnAppearing

【讨论】:

    【解决方案2】:

    假设这是调用页面的 Appearing 事件的受保护方法的覆盖,那么是的,这很好。

    这个方法实际上是一个事件,它是在事件引发之前调用的方法。

    编辑:正如@a​​pineda 在 cmets 中指出的那样,实际上并非如此。

    请注意,您的异步操作将在 任何 Appearing 事件处理程序运行后完成,因为它们在 base.OnAppearing() 中调用。

    【讨论】:

    • 不幸的是,这似乎并非如此。 OnAppearing 只是 ContentPage 的父对象中的一个空虚方法。 Appearing 事件处理程序和此方法在页面的另一个事件中单独调用。链接:github.com/xamarin/Xamarin.Forms/blob/master/Xamarin.Forms.Core/…
    • @apineda 感谢您注意到这一点。我已经编辑了我的答案。
    【解决方案3】:

    如果您正在处理事件(OnAppear 本质上是 Appear 的回调),这完全没问题。

    返回类型一个异步方法应该返回一个任务、任务或 无效。

    如果方法没有返回任何其他的,则指定任务返回类型 价值。

    如果方法需要返回值,则指定Task,其中 TResult 是返回的类型(例如 int)。

    void 返回类型主要用于需要 它。调用返回 void 的异步方法的代码不能等待 结果。

    在可能的情况下简短地使用任务,但在与事件处理程序相关的情况下,只需使用 void。

    如果您需要调用异步,那么您可以根据这个问题使用TaskCompletionSource

    Is it possible to await an event instead of another async method?

    private TaskCompletionSource<object> continueClicked;
    
    private async void Button_Click_1(object sender, RoutedEventArgs e) 
    {
      // Note: You probably want to disable this button while "in progress" so the
      //  user can't click it twice.
      await GetResults();
      // And re-enable the button here, possibly in a finally block.
    }
    
    private async Task GetResults()
    { 
      // Do lot of complex stuff that takes a long time
      // (e.g. contact some web services)
    
      // Wait for the user to click Continue.
      continueClicked = new TaskCompletionSource<object>();
      buttonContinue.Visibility = Visibility.Visible;
      await continueClicked.Task;
      buttonContinue.Visibility = Visibility.Collapsed;
    
      // More work...
    }
    
    private void buttonContinue_Click(object sender, RoutedEventArgs e)
    {
      if (continueClicked != null)
        continueClicked.TrySetResult(null);
    }
    

    参考文献

    https://developer.xamarin.com/guides/cross-platform/advanced/async_support_overview/

    https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

    https://developer.xamarin.com/guides/ios/user_interface/controls/part_2_-_working_with_the_ui_thread/

    【讨论】:

    • 他在问题中表示他知道它主要用于事件处理程序。您基本上是在重申他所说的他已经知道的内容。
    • 这是一个非常详细的答案,但他只是在问他的具体用途是否可以接受。他展示的方法似乎是为Page 调用Appearing 事件的方法,因此本质上是一个事件本身。这意味着在这里使用 async void 应该没问题。
    • 我在第一版中说过“在可能的情况下使用短期任务,但在与事件处理程序相关的情况下,只需使用 void。”仅供参考 OnAppearing 是一个事件回调。
    【解决方案4】:

    我用这种方式

    protected override OnAppearing(){
        base.OnAppearing();
    
        Task.Run(async()=>{
            try
            {
                // Use VM and Binding... 
                MyVM.Data = (await GoGetTheData ()).Stuff;
            }
            catch (Exception e)
            {
    
                // Here I think you should use Device.BeginInvokeOnMainThread();
                MyVm.Data = "Narg";
            }
        });
    }
    

    在某个地方……

    MyText.BindingContext = MyVm;
    MyText.SetBinding(Label.TextProperty, "Data");
    

    【讨论】:

    • 这将引发跨线程异常,因为您无法从后台线程访问 UI 对象...
    • 此外,就使用它的后果而言,它与 async void 方法几乎相同。
    • 最好的选择是使用“绑定”。他也可以尝试使用 Device.BeginInvokeOnMainThread();但我认为 UI 可能会冻结
    • 无论哪种情况,用户界面都不会冻结。 Task.Run 不适用于已经异步的方法。它用于将 CPU 绑定的工作推送到后台线程,以避免冻结主线程。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2016-04-20
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-07-13
    • 2018-08-02
    • 2016-05-27
    相关资源
    最近更新 更多