从同步方法中的异步方法返回结果
本文关键字:返回 结果 异步方法 同步方法 | 更新日期: 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();
}