MVVM Light Вызов асинхронного метода для свойства изменен? - PullRequest
1 голос
/ 27 марта 2019

Так что я довольно много копаю и нигде не смог найти никакого реального окончательного ответа на этот вопрос.

Я пишу приложение, используя MVVM Light и WPF. У меня есть сервис, который внедряется в мой ViewModel, который проверяет действительность определенного свойства, которое установлено. Сервис выполняет веб-вызов, и он является асинхронным. Вызову не нужно останавливать выполнение приложения, и он предназначен исключительно для визуальной обратной связи с пользователем относительно действительности введенного значения.

Таким образом, мне удалось взломать что-то вместе, чтобы заставить его работать, но это выглядит несколько хакерским.

Как правильно выполнить асинхронный метод при изменении свойства, не прибегая к чему-то вроде async void?

Вот что у меня сейчас есть.

    public string OmmaLicenseNumber
    {
        get => _ommaLicenseNumber;
        set
        {
            Set(() => OmmaLicenseNumber, ref _ommaLicenseNumber, value);
            Patient.OmmaLicenseNumber = value;

            var _ = CheckLicenseValid();
        }
    }

    private async Task CheckLicenseValid()
    {
        var valid = await _licenseValidationService.IsValidAsync(OmmaLicenseNumber);

        // We don't want the UI piece showing up prematurely. Need 2 properties for this;
        LicenseValid = valid;
        LicenseInvalid = !valid;
    }

Если я просто попытаюсь вызвать .Result в асинхронном методе, это приведет к тупику, который требует перезапуска приложения для исправления. И хотя то, что у меня работает, я на самом деле не фанат. Какие у меня есть другие варианты?

Ответы [ 3 ]

2 голосов
/ 27 марта 2019

Обработчики событий позволяют использовать async void

Ссылка Async / Await - лучшие практики асинхронного программирования

public string OmmaLicenseNumber {
    get => _ommaLicenseNumber;
    set {
        Set(() => OmmaLicenseNumber, ref _ommaLicenseNumber, value);
        Patient.OmmaLicenseNumber = value;
        //Assuming event already subscribed 
        //i.e. OmmaLicenseNumberChanged += OmmaLicenseNumberChanged;
        OmmaLicenseNumberChanged(this, 
            new LicenseNumberEventArgs { LicenseNumber = value }); //Raise event
    }
}

private event EventHandler<LicenseNumberEventArgs> OmmaLicenseNumberChanged = delegate { };
private async void OnOmmaLicenseNumberChanged(object sender, LicenseNumberEventArgs args) {
    await CheckLicenseValid(args.LicenseNumber); //<-- await async method call
}

private async Task CheckLicenseValid(string licenseNumber) {
    var valid = await _licenseValidationService.IsValidAsync(licenseNumber);

    // We don't want the UI piece showing up prematurely. Need 2 properties for this;
    LicenseValid = valid;
    LicenseInvalid = !valid;
}

//...

public class LicenseNumberEventArgs : EventArgs {
    public string LicenseNumber { get; set; }
}

Думаю ли я, что этонемного тяжелыми руками?

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

Может ли это быть реорганизовано для более простого вызова вспомогательного / служебного метода?

Да.Может выглядеть как ожидаемый шаблон обратного вызова с использованием выражений для получения значения для проверки

2 голосов
/ 27 марта 2019

Проблема здесь не в том, как запустить задачу async без наблюдения, а в том, что вы делаете с исключениями. Я говорю это, потому что они могут появиться, когда задача будет очищена.

В идеале просто покажите следующему читателю, что он получает. Так как вы против использования async void

Вариант 1

// running an async method unobserved 
Task.Run(async () =>
{
   try
   {
      await DoSomethingAsync();
   }
   catch (Exception e)
   {
       // Deal with unobserved exceptions 
       // or there will be dragons
   }  
});

Примечание : это технически разгрузка (он потеряет контекст, ОСТЕРЕГАЙТЕСЬ), и async лямда в любом случае делает его асинхронным, однако вам придется иметь дело с исключения в любом случае, хотя

Вариант 2 и более спорно

public async void DoSomethingFireAndForget()
{
   try
   {
      await DoSomethingAsync();
   }
   catch (Exception e)
   {
      // Deal with unobserved exceptions 
      // or the will be dragons
   }  
}

Вариант 3 лучшее из обоих миров.

Примечание : Используйте все, что вам нужно, чтобы соблюсти исключения, например Action ect

public static class TaskUtils
{

#pragma warning disable RECS0165 // Asynchronous methods should return a Task instead of void
   public static async void FireAndForgetSafeAsync(this Task task,  Action<Exception> onErrors = null)
#pragma warning restore RECS0165 // Asynchronous methods should return a Task instead of void
   {
      try
      {
         await task;
      }
      catch (Exception ex)
      {
         onErrors?.Invoke(ex);
      }
   }
}

Может быть Стивен Клири произойдет это позже и даст вам более красноречивое объяснение

1 голос
/ 27 марта 2019

Взгляните на класс NotifyTask, написанный Стивеном Клири.

Это хороший способ обработки асинхронных операций в конструкторах и свойствах.

Вы можете изменить свой код следующим образом:

private NotifyTask _OmmaLicenseNumberChangedNotifyTask
    = NotifyTask.Create(Task.CompletedTask);
public NotifyTask OmmaLicenseNumberChangedNotifyTask
{
    get => this._OmmaLicenseNumberChangedNotifyTask;
    set
    {
        if (value != null)
        {
            this._OmmaLicenseNumberChangedNotifyTask = value;
            OnPropertyChanged("OmmaLicenseNumberChangedNotifyTask");
        }
    }
}

public string OmmaLicenseNumber
{
    get => _ommaLicenseNumber;
    set
    {
        Set(() => OmmaLicenseNumber, ref _ommaLicenseNumber, value);
        Patient.OmmaLicenseNumber = value;

        OmmaLicenseNumberChangedNotifyTask = NotifyTask.Create(CheckLicenseValid());
    }
}

Тогда вы можете забыть о Task или вы можете привязать элементы вашего интерфейса к свойствам NotifyTask, например, IsCompleted длясделать что-то видимым только в том случае, если Task завершен.

...