Возврат результата из асинхронного метода в методе синхронизации

В моем приложении у меня есть метод async Save () типа Task<bool>, который через bool сигнализирует об успешном сохранении. В Save () могут происходить всевозможные вещи, он вызывает другой уровень, где обрабатываются исключения, отображаются возможные диалоги и т. Д., Но это не имеет значения, все, что меня волнует, - это результат bool.

Теперь мне нужно вызвать этот метод из неасинхронного метода (который является переопределением используемого фреймворка, поэтому я не могу просто сделать его асинхронным)

Код выглядит примерно так:

public override void SynchronousMethodFromFramework()
{
    bool result = false;
    Task.Run(async () => result = await Save());
    return result;
}

Проблема в том, что результат возвращается ДО завершения сохранения (поэтому всегда ложно). Как это решить? Я пробовал Task.WaitAll (), .Result, .ConfigureAwaiter (false), но все, что я делаю, кажется, полностью замораживает мое приложение.

Еще немного информации:

Используемая среда WPF - Caliburn.Micro. Моя MainviewModel - это Conductor<IScreen>.Collection.OneActive, которая выполняет несколько моделей просмотра в элементе управления Tab. Каждая ViewModel представляет собой своего рода экран редактирования. Когда конечный пользователь закрывает приложение (через правый верхний красный крестик), я хочу перебрать все вкладки, чтобы увидеть, есть ли в них ожидающие изменения. Код модели mainview:

 public override void CanClose(Action<bool> callback)
    {
        //for each tab, go to it and try to close it. 
        //If pending changes and close is not succeeded (eg, user cancels), abort aplication close
        bool canclose = false;
        Action<bool> result = b => canclose = b;
        for (int i = Items.Count - 1; i >= 0; i--)
        {
            var screen = Items[i];
            screen.CanClose(result);
            if (!canclose)
            {
                callback(false);
                return;
            }

        }
        callback(true);
    }

Код в моем "Edit" -ViewModels:

    private async Task<bool> SavePendingChanges()
    {
        if (!Entity.HasDirtyContents())
            return true;
        bool? dialogResult = DialogProvider.ShowMessageBox("Save changes",
            "There are pending changes, do you want to save them ?", MsgBoxButton.YesNoCancel);
        if (dialogResult == null)
            return false;//user cancelled 
        if (dialogResult == false)
            return true;//user doesn't want to save, but continue
        //try to save; if save failed => return false
        return await (Save());
    }

    public override void CanClose(Action<bool> callback)
    {
        var task = SavePendingChanges();
        task.Wait();
        bool result = task.Result;
        callback(result);
    }

CanClose - это неасинхронный каркасный метод, предоставляемый CM ...


person hcd    schedule 25.09.2015    source источник
comment
Google - ваш друг: msdn.microsoft.com/ en-us / library / dd235635 (v = vs.110) .aspx. Просто создайте переменную из своей задачи и .Wait() для нее.   -  person mortb    schedule 25.09.2015
comment
Если вы заблокируете метод в потоке пользовательского интерфейса, пользовательский интерфейс зависнет. Это довольно фундаментально, с этим ничего не поделаешь.   -  person usr    schedule 25.09.2015
comment
Как уже упоминалось, я пробовал это, и у меня полностью зависает приложение. Я подозреваю, что это связано с тем, что где-то в цепочке метода Save () установлена ​​переменная IsBusy, привязанная к видимости элемента управления WPF UI, BizzySpinner, чтобы указать, что что-то происходит.   -  person hcd    schedule 25.09.2015
comment
Хорошо, я должен был сказать это сразу: вам нужно предоставить более подробную информацию. Если вы хотите разморозить пользовательский интерфейс, вы не можете заблокировать этот поток. Чтобы что-то порекомендовать, вам нужно дать больше контекста.   -  person usr    schedule 25.09.2015
comment
хорошо, я отредактирую OP, чтобы сделать его более понятным   -  person hcd    schedule 25.09.2015
comment
Почему бы вам просто не сделать: public override async void SynchronousMethodFromFramework ()? Я не помню, должна ли асинхронность быть до или после переопределения. Вы должны иметь возможность сделать это, даже если это переопределенный метод. Проверь это.   -  person MistyK    schedule 25.09.2015
comment
Невозможно сохранить сохраненное / несохраненное состояние каждой вкладки в более доступном месте? Тогда вы могли бы значительно повысить скорость действия Close, если бы основной код просто вызывал, предположительно, Dictionary<Tab, Status>.Any(pair => pair.Value).   -  person tomab    schedule 25.09.2015


Ответы (1)


Спасибо Стивену Клири за ответ, но тем временем я также нашел решение, которое, похоже, работает во всех сценариях, которые я пробовал (откройте несколько вкладок, у некоторых не было изменений, у некоторых были изменения, а у других были изменения с оптимистичными исключениями параллелизма, которые запускают собственный рабочий процесс, открывая диалоговые окна для их решения). И все сценарии сработали, поэтому мне интересно, хорошее ли это решение, или я что-то упускаю из виду или делаю какой-то ужасный взлом?

Мой подход:

Поскольку проблема заключается в несинхронизирующем методе CanClose в CM-framework (на самом деле проблема заключается в обратном вызове), я перенаправил его в свою собственную базу ViewModel следующим образом:

public abstract class ViewModelBase : IScreen
{
        //sealed so the users of the viewmodelbase cannot accidentally override it
        public async override sealed void CanClose(Action<bool> callback)
        {
            callback(await CanClose());
        }

        //default implementation is always closable
        public async virtual Task<bool> CanClose()
        {
            return true;
        }

}

Итак, весь "CanClose" из фреймворка теперь перенаправлен через мой собственный асинхронный метод CanClose типа Task (обратите внимание на "async" в этом переопределении - спасибо за подсказку)

Затем в модели MainView, которая выполняет вкладки, мой собственный async CanClose переопределяется как:

 public async override Task<bool> CanClose()
    {
        for (int i = Items.Count - 1; i >= 0; i--)
        {
            var screen = Items[i] as ViewModelBase;
            var canclose = await screen.CanClose();
            if (!canclose) return false;
            Items.Remove(screen);//remove it from the tabs so it's not visible anymore
        }
        return true;
    }

и, наконец, в моих EditViewmodels я переопределил CanClose как:

  public override async Task<bool> CanClose()
    {
        return await SavePendingChanges();
    }

   public async Task<bool> SavePendingChanges()
        {
            if (!Entity.HasDirtyContents())
                return true;
            bool? dialogResult = DialogProvider.ShowMessageBox("Save changes",
                "There are pending changes, do you want to save them ?", MsgBoxButton.YesNoCancel);
            if (dialogResult == null)
                return false;//user cancelled 
            if (dialogResult == false)
                return true;//user doesn't want to save, but continue
            //try to save; if save failed => return false
            return await Save();
        }
person hcd    schedule 25.09.2015