【问题标题】:What is the best way to fix "A method was called at an unexpected time" error修复“在意外时间调用了方法”错误的最佳方法是什么
【发布时间】:2015-10-22 18:20:52
【问题描述】:

首先,我已经看过this question,并且我(有点)理解为什么我会遇到这个异常,我想知道修复它的最佳方法是什么。我的代码看起来有点像这样(这是一个 WinRT 应用程序):

//Here is my App constructor:
public App()
{
    this.InitializeComponent();
    this.Suspending += this.OnSuspending;

    //Initializing the model
    _model = new Model();
    _model.LoadData();
}

//the LoadData method looks like this:
public async void LoadData()
{
    StorageFolder folder = Windows.ApplicationModel.Package.Current.InstalledLocation;
    StorageFile file = await folder.GetFileAsync(@"Assets\Data.json");
    string data = await FileIO.ReadTextAsync(file);

    var dataList = JsonConvert.DeserializeObject<List<MyDataClass>>(data);
    // From time to time (pretty rarely, honestly) this line causes the
    // "A method was called at an unexpected time" thing:
    var dispatcher = CoreApplication.MainView.CoreWindow.Dispatcher;
    foreach (var item in dataList)
    {
        //do some stuff
        //<...>
        await dispatcher.RunAsync(Windows.UI.Core.CoreDispatcherPriority.Normal,
        () => {
            //do some stuff in the UI thread
        });                
    }
}

显然,拥有LoadData 方法async void 并不是最好的解决方案。但是,如您所见,我必须在其中执行一些异步函数调用(从文件中读取数据)。现在我能想到两种可能的解决方案:

  1. LoadData更改为public async Task LoadData(),并将应用程序构造函数中的调用更改为_model.LoadData().GetAwaiter().GetResult();,以便同步运行;
  2. LoadData更改为public void LoadData(),并将其中的所有await调用更改为使用awaiter,例如StorageFile file = folder.GetFileAsync(@"Assets\Data.json").GetAwaiter().GetResult()

其中哪一个是更好的解决方案,或者,更好的是,有没有其他合适的方法在应用程序启动时运行异步代码?另外,为什么调度程序行会出现“A method was called at an unexpected time”错误?

【问题讨论】:

  • 为什么需要从构造函数初始化模型?有一种更合适的方法来做到这一点。调用 async Task LoadData() 方法,例如 App.xaml.cs 的 protected async override void OnLaunched(LaunchActivatedEventArgs e) 方法,等待:await LoadData()

标签: c# asynchronous windows-runtime


【解决方案1】:

哪个是更好的解决方案

两种预期的解决方案阻塞 - 具体来说,它们阻塞 I/O。让我们考虑一下阻塞,但这次不是应用程序的“我需要这些数据才能显示我想要的内容”的观点,而是从运行时的角度考虑。

运行时绝对,肯定不应该阻塞用户界面。阻止 UI 会阻止用户,这简直是不可接受的用户体验。这就是为什么整个移动世界都是异步优先的;这让许多桌面开发人员感到震惊。桌面应用应该异步优先,但移动应用必须异步优先。

因此,从运行时的角度来看,当它启动一个应用程序时,该应用程序必须显示一些东西。立即地。现在。 I/O 不是一个选项。它不必是完美的应用主屏幕,但应用必须同步显示某些东西

这就是阻止不起作用的原因。自动应用商店代码分析应该拒绝这两种阻止方法。

有没有其他合适的方式在应用启动时运行异步代码?

是的,但应用程序必须妥协。在向用户显示漂亮的、完整的第一个屏幕之前,它不能在执行 I/O 时阻塞 UI。一些桌面应用可以避免这种情况(即使它们也不应该这样做),但这不是移动应用的选择。

相反,应用应该(同步)加载并显示“正在加载...”状态。这可以是一个启动屏幕,或者只是带有“正在加载...”消息或微调器代替数据的实际主屏幕。这满足了运行时的要求。当然,在返回之前,应用程序还应该启动异步操作来检索数据,然后更新 UI 以显示它真正想要显示的内容(常规视图完成数据)。

这可以像使用异步事件一样简单(正如@marcinax 评论的那样):

protected override async void OnLaunched(...)
{
  ... // Initialize UI to "Loading" state.

  _model = new Model();
  await _model.LoadData();

  ... // Update UI with data in _model.
}

就个人而言,我更喜欢将所有 UI 代码保留在 UI 层中(不是Model 的一部分),因此如果您有任何显式(非数据绑定)UI 初始化与数据有关,那么它应该去在// Update UI之后。

但是,如果您发现自己在应用中多次执行此操作(例如,每个窗口/页面都需要加载数据),那么您可能更喜欢我的 MSDN 文章中描述的asynchronous data-binding solution

另外,为什么调度程序行会出现“A method was called at an unexpected time”错误?

我不完全确定,但我怀疑调度程序尚未运行。

将 UI 代码保留在模型/VM 层之外的一个非常好的副作用是不再需要显式的 CoreDispatcher 代码。总会有更好的解决方案。

【讨论】:

  • 感谢您的详尽回答。作为一个附带问题,将基类的非异步方法重写为异步是否总是可以的?据我了解,async 不是函数签名的一部分。
  • @SingerOfTheFall:在这种情况下,我们将OnLaunched 方法视为一个事件。从逻辑上讲,这是一个事件; API 只是没有反映这一点。在大多数情况下,这不会很好地工作。
猜你喜欢
  • 1970-01-01
  • 2012-10-17
  • 2021-12-31
  • 2011-04-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多