Асинхронный метод, вызываемый в ViewModel, вызывает взаимоблокировку

#c# #asynchronous #xamarin #xamarin.forms #prism

#c# #асинхронный #xamarin #xamarin.forms #призма

Вопрос:

Я делаю запросы к Github Api, поэтому у меня есть асинхронные методы, они выполняют эту работу. Перед этим я всегда вызывал they в методе, который вызывает из command(фактически DelegateCommand ). Но теперь я хочу выполнить запрос в ViewModel, потому что мне нужно отобразить список на странице. Я использую Prism для подключения view и viewmodel. Поскольку я не могу сделать viewmodel асинхронным, я не могу использовать await word, поэтому я попытался сделать что-то вроде получения результата из task или task.wait. Но с этим я получаю тот же результат. Моя остановка приложения работает с белым дисплеем, когда он запрашивал. Я прочитал некоторую информацию об этом и понял, что вызов асинхронного метода в не асинхронном методе плохой, и это вызывает взаимоблокировку, но я не знаю, что с этим делать. И я думаю, что взаимоблокировка приводит к тому, что приложение перестает работать. Вот метод, в котором приложение умирает:

 public async Task<IEnumerable<RepositoryModel>> GetRepositoriesAsync()
{
    try
    {
        var reposRequest = new RepositoryRequest { Sort = RepositorySort.FullName };
        var gitHubRepos = await _gitHubClient.Repository.GetAllForCurrent(reposRequest);  //async request, don't say about name convention, it is not my method.
        var gitRemoteRepos = new List<RepositoryModel>();
        foreach ( var repository in gitHubRepos )
        {
            var repos = new RepositoryModel();
            repos.RepositoryTypeIcon = GetRepositoryTypeIcon(repository);
            gitRemoteRepos.Add(repos);
        }
        return gitRemoteRepos;
    }
    catch ( WebException ex )
    {
        throw new Exception("Something wrong with internet connection, try to On Internet "   ex.Message);
    }
    catch ( Exception ex )
    {
        throw new Exception("Getting repos from github failed! "   ex.Message);
    }
}
  

И вот viewmodel:

 public class RepositoriesPageViewModel : BindableBase
{
    private INavigationService _navigationService;
    private readonly Session _session;
    public ObservableCollection<RepositoryModel> Repositories { get; }
    private readonly RepositoriesManager _repositoriesManager;
    public RepositoriesPageViewModel(INavigationService navigationService, ISecuredDataProvider securedDataProvider)
    {
        _navigationService = navigationService;
        var token = securedDataProvider.Retreive(ConstantsService.ProviderName, UserManager.GetLastUser());
        _session = new Session(UserManager.GetLastUser(), token.Properties.First().Value);
        var navigationParameters = new NavigationParameters { { "Session", _session } };

        _repositoriesManager = new RepositoriesManager(_session);
        var task = _repositoriesManager.GetRepositoriesAsync();
        //task.Wait();
        Repositories = task.Result as ObservableCollection<RepositoryModel>;
    }
}
  

Комментарии:

1. Не используйте TPL в конструкторе

2. Не используйте Task.Result из основного потока (GUI).

3. blog.stephencleary.com/2012/07/dont-block-on-async-code.html

Ответ №1:

Я рекомендую использовать my NotifyTask<T> type, который предоставляет оболочку с возможностью привязки к данным Task<T> . Я более подробно объясняю этот шаблон в своей статье об асинхронной привязке данных MVVM.

 public class RepositoriesPageViewModel : BindableBase
{
  private INavigationService _navigationService;
  private readonly Session _session;
  public NotifyTask<ObservableCollection<RepositoryModel>> Repositories { get; }
  private readonly RepositoriesManager _repositoriesManager;
  public RepositoriesPageViewModel(INavigationService navigationService, ISecuredDataProvider securedDataProvider)
  {
    _navigationService = navigationService;
    var token = securedDataProvider.Retreive(ConstantsService.ProviderName, UserManager.GetLastUser());
    _session = new Session(UserManager.GetLastUser(), token.Properties.First().Value);
    var navigationParameters = new NavigationParameters { { "Session", _session } };

    _repositoriesManager = new RepositoriesManager(_session);
    Repositories = NotifyTask.Create(GetRepositoriesAsync());
  }
}

private async Task<ObservableCollection<RepositoryModel>> GetRepositoriesAsync()
{
  return new ObservableCollection<RepositoryModel>(await _repositoriesManager.GetRepositoriesAsync());
}
  

Обратите внимание, что при таком подходе ваша привязка данных будет использоваться Repositories.Result для доступа к фактической коллекции. Также доступны другие свойства, в первую очередь Repositories.IsCompleted и Respositories.IsNotCompleted для отображения / скрытия занятых блесен.

Комментарии:

1. Большое вам спасибо. И для ваших статей тоже.