Xamarin Async ViewDidAppear вызывается во время загрузки ViewDidLoad


Я пытаюсь инициализировать модель представления на ViewDidLoad. Мне нужно вызвать некоторые асинхронные методы в коде инициализации ViewModel, поэтому я переместил асинхронный код из конструктора васинхронный фабричный метод .

Я пометил ViewDidLoad и ViewWillAppear как async void в моем подклассе UIViewController, но по какой-то причине, пока строка 4 выполняет ViewWillAppear запускается, а строка 11 бросает NullReferenceException, потому что ViewModel еще не инициализирован.

Я подозреваю, что Xamarin не могу дождаться завершения ViewDidLoad, потому что это async void, но я должен использовать async void здесь, потому что он переопределяет метод.

MyCustomUiViewController.cs

1  public override async void ViewDidLoad()
2  {
3      base.ViewDidLoad();
4      ViewModel = await ViewModel.CreateAsync();
5      OtherStuff();
6  }
7 
8  public override async void ViewWillAppear(bool animated)
9  {
10     base.ViewWillAppear(animated);
11     ViewModel.SomeMethod(); // <-- NullReferenceException
12     AttachViewModelToViewBindings();
13 }

Я готов изменить архитектуру, если есть лучший шаблон для создания экземпляра асинхронной ViewModel.

2 2

2 ответа:

Вот обобщенный паттерн, который мы использовали (извлеченный в эту суть). Это позволяет создать контроллер, который наследует от AsyncInitializationController, а затем переопределяет, например, ViewDidLoadAsync. Код структурирован таким образом, что каждый последующий метод жизненного цикла ожидает завершения предыдущего.

Хотя у нас не было необходимости в асинхронности ViewDidDisappear, я уверен, что вы могли бы работать и с этим шаблоном.

using System;
using System.Threading.Tasks;
using UIKit;

namespace Seanfisher.Gists
{
    public abstract class AsyncInitializationController : UIViewController
    {
        Task _viewDidLoadAsyncTask = Task.CompletedTask;
        public virtual Task ViewDidLoadAsync()
        {
            return _viewDidLoadAsyncTask;
        }

        public sealed override async void ViewDidLoad()
        {
            try
            {
                base.ViewDidLoad();
                _viewDidLoadAsyncTask = ViewDidLoadAsync();
                await _viewDidLoadAsyncTask;
            }
            catch (Exception e)
            {
                // Handle
            }
        }

        Task _viewWillAppearAsyncTask = Task.CompletedTask;
        public virtual Task ViewWillAppearAsync()
        {
            return _viewWillAppearAsyncTask;
        }

        public sealed override async void ViewWillAppear(bool animated)
        {
            try
            {
                await _viewDidLoadAsyncTask;
                base.ViewWillAppear(animated);
                _viewWillAppearAsyncTask = ViewWillAppearAsync();
                await _viewWillAppearAsyncTask;
            }
            catch (Exception e)
            {
                // Handle
            }
        }

        Task _viewDidAppearAsyncTask = Task.CompletedTask;
        public virtual Task ViewDidAppearAsync()
        {
            return _viewDidAppearAsyncTask;
        }
        public sealed override async void ViewDidAppear(bool animated)
        {
            try
            {
                await _viewDidLoadAsyncTask;
                await _viewWillAppearAsyncTask;

                base.ViewDidAppear(animated);
                _viewDidAppearAsyncTask = ViewDidAppearAsync();
                await _viewDidAppearAsyncTask;
            }
            catch (Exception e)
            {
                // Handle
            }
        }
    }
}

Бодангли прав.

Методы не будут называться асинхронными только потому, что вы отмечаете их как асинхронные.

Кроме того, всегда следует избегать "асинхронной пустоты". Прочтите это: https://msdn.microsoft.com/en-us/magazine/jj991977.aspx

Лучший шаблон для решения этой проблемы объясняется здесь: http://blog.stephencleary.com/2013/01/async-oop-2-constructors.html

Который должен быть примерно таким: (непроверенный)

public override void ViewDidLoad()
{
   base.ViewDidLoad();
   Initialization = InitializeAsync();

   OtherStuff();
}

public Task Initialization { get; private set; }

private async Task InitializeAsync()
{
    // Do our own initialization (synchronous or asynchronous).
    await Task.Delay(100);
}