【问题标题】:Return result from Async Method within a Sync method从 Sync 方法中的 Async 方法返回结果
【发布时间】:2015-09-25 22:27:36
【问题描述】:

在我的应用程序中,我有一个 Task<bool> 类型的异步 Save() 方法,如果保存成功,它将通过 bool 发出信号。各种事情都可能发生在 Save() 中,它通过处理异常的另一层调用,显示可能的对话框等,但这没关系,我只关心布尔结果。

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

代码看起来有点像:

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,它在 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);
    }

我的“编辑”-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 提供的非异步框架方法 ...

【问题讨论】:

  • 谷歌是你的朋友:msdn.microsoft.com/en-us/library/dd235635(v=vs.110).aspx。只需从您的任务中创建一个变量并为它创建一个.Wait()
  • 如果你在 UI 线程上阻塞了一个方法,那么 UI 就会冻结。这是非常基本的,你无能为力。
  • 如前所述,我已经尝试过了,我完全挂起应用程序。我怀疑这是因为在 Save() 方法链的某个地方,设置了一个变量“IsBusy”,它数据绑定到 WPF UI 控件的可见性,一个“BizzySpinner”表示正在发生的事情。
  • 好的,我应该马上说:您需要提供更多详细信息。如果要解冻 UI,则不能阻止该线程。为了推荐一些东西,你需要提供更多的背景信息。
  • 你为什么不这样做: public override async void SynchronousMethodFromFramework() ?我不记得异步应该在覆盖之前还是之后。即使它是被覆盖的方法,您也应该能够做到。检查这个。

标签: c# asynchronous caliburn.micro


【解决方案1】:

适当的解决方案是使SynchronousMethodFromFramework 异步,要么让它返回Task,要么使用deferrals 之类的东西(正如我在我的博客中描述的那样)。所以绝对让框架作者知道你的需求

与此同时,您可以使用 one of the hacks from my article on async brownfield development 绕过它。


最简单的解决方案是让您的 Save 方法成为纯后台方法 - 毕竟,如果它作为后台任务运行,它不应该通过任何更新“进入” UI 线程。如果您的代码在几个地方调用 Save - 一次用于“常规”保存,然后是另一个“仅最后机会同步”保存 - 那么您可以使用 IProgress&lt;T&gt; 更新 UI 而不是直接访问 ViewModel 属性和/或使用Dispatcher。如果你的代码在这里只调用Save,那么就彻底删除所有的UI更新。

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

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

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

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

自从我在 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();
}

【讨论】:

    【解决方案2】:

    感谢 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 方法重新路由(请注意此覆盖上的“async” - 感谢您的提示)

    然后在执行选项卡的 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();
            }
    

    【讨论】:

      猜你喜欢
      • 2014-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-05-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-01-20
      相关资源
      最近更新 更多