从同步方法中的异步方法返回结果

本文关键字:返回 结果 异步方法 同步方法 | 更新日期: 2023-09-27 18:14:27

在我的应用程序中,我有一个异步保存()方法,类型为Task<bool>,通过bool将表明保存是否成功。各种各样的东西都可以发生在Save()中,它通过另一层调用异常处理,可能的对话框显示等,但这并不重要,我所关心的是bool结果。

现在我必须从一个非异步方法中调用这个方法(这是一个来自使用的框架的覆盖,所以我不能让它async)

代码看起来有点像:
public override void SynchronousMethodFromFramework()
{
    bool result = false;
    Task.Run(async () => result = await Save());
    return result;
}

问题是,结果在Save完成之前返回(因此总是false)。它如何解决这个问题?我已经尝试了Task.WaitAll(), . result, . configureawaiter (false),但我所做的一切似乎完全冻结了我的应用程序。

更多信息:

使用的WPF框架是Caliburn.Micro。我的MainviewModel是一个Conductor<IScreen>.Collection.OneActive,它在一个Tabcontrol中执行多个视图模型。每个ViewModel都是某种编辑屏幕。当最终用户关闭应用程序时(通过右上方的红色X),我想遍历所有选项卡,看看它们是否有未决的更改。mainviewmodel的代码:

 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);
    }

从同步方法中的异步方法返回结果

适当的解决方案是使SynchronousMethodFromFramework异步,通过让它返回Task或使用延迟之类的东西(如我在博客上描述的)。所以一定要让框架作者知道你的需求

与此同时,你可以使用我关于async棕地开发的文章中的一个技巧来破解它。


最简单的解决方案是让你的Save方法成为一个纯粹的后台方法-毕竟,如果它作为后台任务运行,它不应该"触及"任何更新的UI线程。如果你的代码在几个地方调用Save——一次是"常规"保存,然后是"最后一次同步"保存——那么你可以使用IProgress<T>来更新UI,而不是直接访问ViewModel属性和/或使用Dispatcher。如果你的代码在这里只调用Save,那么就完全删除所有的UI更新。

如果你可以让Save成为一个真正的后台操作,那么你可以直接阻塞:

return Task.Run(() => Save()).GetAwaiter().GetResult();

但是如果你不能做到这一点(我假设你已经考虑过并拒绝了它),那么你可以调用一些严肃的黑魔法来改变运行时间。也就是说,使用嵌套消息循环(Lucifer Enterprises的注册商标)。

开发者General的警告:嵌套的消息循环是邪恶的。邪恶,邪恶,邪恶!副作用包括精神错乱和死亡。这段代码的未来维护者很有可能会变得暴力并追捕你。

我在WPF中嵌套消息循环已经有一段时间了,但我相信这应该可以做到:

private async Task<bool> EvilSaveAsync(DispatcherFrame frame)
{
  try
  {
    return await Task.Run(() => Save());
  }
  finally
  {
    frame.Continue = false;
  }
}
public override void SynchronousMethodFromFramework()
{
  var frame = new DispatcherFrame();
  var task = EvilSaveAsync(frame);
  Dispatcher.PushFrame(frame);
  return task.GetAwaiter().GetResult();
}

感谢Stephen Cleary的回答,但与此同时,我也找到了一个解决方案,似乎在我尝试过的所有场景中都有效(打开多个选项卡,有些没有变化,有些有变化,而其他人则有乐观并发异常的变化,触发他们自己的工作流程,打开对话框来解决这些)。所有的场景都起作用了,所以我想知道这是一个好的解决方案,还是我忽视了一些东西或做了一些可怕的黑客?

我的方法:

由于问题是cm框架中的close非同步方法(实际上,问题是回调操作),我已经在我自己的ViewModelbase中将其重新路由为:

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"现在都通过我自己的Task类型的async CanClose方法重新路由(注意这个覆盖上的"async"-谢谢你的提示)

然后在MainViewmodel中执行选项卡,我自己的异步关闭被覆盖为:

 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中,我已经覆盖了close:

  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();
        }