从Sync方法中的Async方法返回结果

时间:2015-09-25 09:33:09

标签: c# asynchronous caliburn.micro

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

现在我必须在非异步方法中调用此方法(这是从使用过的框架中覆盖,所以我不能只是让它异步)

代码看起来有点像:

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

我的&#34;编辑&#34; -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);
    }

&#34; CanClose&#34;是CM提供的非异步框架方法......

2 个答案:

答案 0 :(得分:1)

正确的解决方案是让SynchronousMethodFromFramework异步,方法是让它返回Task或使用类似deferrals的内容(正如我在博客中描述的那样) 。所以大多数让你的需求知道给框架作者。

与此同时,您可以使用one of the hacks from my article on async brownfield development来解决问题。

最简单的解决方案是使您的Save方法成为纯粹的后台方法 - 毕竟,如果它作为后台任务运行,它不应该“进入”具有任何更新的UI线程。如果您的代码在几个地方调用Save - 一次用于“常规”保存,然后另一个“仅限同步最后机会”保存 - 那么您可以使用IProgress<T>更新UI而不是直接访问ViewModel属性和/或使用Dispatcher。如果您的代码仅在此处调用Save,则只需完全删除所有UI更新。

如果您可以使Save成为真正的后台操作,那么您可以阻止:

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

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

  

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

答案 1 :(得分:0)

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

我的方法:

由于问题是CM框架中的CanClose非同步方法(实际上,存在回调操作的问题),我已经在我自己的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的CanClose方法重新路由(注意这个覆盖的“异步” - 谢谢你的提示)

然后在进行选项卡的MainViewmodel中,我自己的异步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();
        }