wpf 从 Dispatcher 上调用的异步操作返回值
声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow
原文地址: http://stackoverflow.com/questions/13012587/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me):
StackOverFlow
Returning value from async Action invoked on Dispatcher
提问by Jaded
I'm developing a WPF XBAP application that provides API to user through JavaScript by using BrowserInteropHelper. After rewriting managed part in new async-await fashion there is a need to wait until async operation is finished without making calling method async Task itself. I have :
我正在开发一个 WPF XBAP 应用程序,它使用 BrowserInteropHelper 通过 JavaScript 向用户提供 API。在以新的 async-await 方式重写托管部分后,需要等待异步操作完成,而无需调用方法 async Task 本身。我有 :
public string SyncMethod(object parameter)
{
var sender = CurrentPage as MainView; // interop
var result = string.Empty;
if (sender != null)
sender.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(async () =>
{
try
{
result = await sender.AsyncMethod(parameter.ToString());
}
catch (Exception exception)
{
result = exception.Message;
}
}));
return result;
}
This method returns string.Empty because that happens right after execution. I've tried to assign this action to DispatcherOperation and do while(dispatcherOperation.Status != Completed) { Wait(); }, but even after execution the value is still empty. dispatcherOperation.Task.Wait() or dispatcherOperation.Task.ContinueWith() also didn't help.
edit
Also tried
此方法返回 string.Empty 因为它在执行后立即发生。我尝试将此操作分配给 DispatcherOperation 并执行 while(dispatcherOperation.Status != Completed) { Wait(); },但即使执行后该值仍然为空。dispatcherOperation.Task.Wait() 或 dispatcherOperation.Task.ContinueWith() 也没有帮助。
编辑
也试过
var op = sender.Dispatcher.BeginInvoke(...);
op.Completed += (s, e) => { var t = result; };
But t is empty too because assignment happens before awaiting of asynchronous method.
edit
Long story short, SyncMethod JavaScript interop handler helper wrapper, and all operations it runs must be on Dispatcher. AsyncMethod is async Task< T > and works perfectly when executed and awaited from inside application, i.e. inside some button handler. It has a lot of UI-dependent logic (showing status progressbar, changing cursor, etc.).
edit
AsyncMethod :
但是 t 也是空的,因为赋值发生在等待异步方法之前。
编辑
长话短说,SyncMethod JavaScript 互操作处理程序帮助程序包装器,它运行的所有操作都必须在 Dispatcher 上。AsyncMethod 是异步任务< T > 并且在从应用程序内部(即在某些按钮处理程序内部)执行和等待时完美运行。它有很多与 UI 相关的逻辑(显示状态进度条、更改光标等)。
编辑
异步方法:
public async Task<string> AsyncMethod(string input)
{
UIHelpers.SetBusyState(); // overriding mouse cursor via Application.Current.Dispatcher
ProgressText = string.Empty; // viewmodel property reflected in ui
var tasksInputData = ParseInput(input);
var tasksList = new List<Task>();
foreach (var taskInputDataObject in tasksInputData)
{
var currentTask = Task.Factory.StartNew(() =>
{
using (var a = new AutoBusyHandler("Running operation" + taskInputData.OperationName))
{ // setting busy property true/false via Application.Current.Dispatcher
var databaseOperationResult = DAL.SomeLongRunningMethod(taskInputDataObject);
ProgressText += databaseOperationResult;
}
});
tasksList.Add(insertCurrentRecordTask);
}
var allTasksFinished = Task.Factory.ContinueWhenAll(tasksList.ToArray(), (list) =>
{
return ProgressText;
});
return await allTasksFinished;
}
采纳答案by Stephen Cleary
After rewriting managed part in new async-await fashion there is a need to wait until async operation is finished without making calling method async Task itself.
在以新的 async-await 方式重写托管部分后,需要等待异步操作完成,而无需调用方法 async Task 本身。
This is one of the most difficult things you can try to do.
这是您可以尝试做的最困难的事情之一。
AsyncMethod is async Task< T > and works perfectly when executed and awaited from inside application, i.e. inside some button handler. It has a lot of UI-dependent logic
AsyncMethod 是异步任务< T > 并且在从应用程序内部(即在某些按钮处理程序内部)执行和等待时完美运行。它有很多依赖于 UI 的逻辑
And this situation makes it near-impossible.
而这种情况使它几乎不可能。
The problem is that you need to block SyncMethodwhich is running on the UI thread, while allowing AsyncMethodto dispatch to the UI message queue.
问题是您需要阻止SyncMethod在 UI 线程上运行的线程,同时允许AsyncMethod分派到 UI 消息队列。
There are a couple of approaches you could take:
您可以采取以下几种方法:
- Block on the
TaskusingWaitorResult. You can avoid the deadlock problem described on my blogby usingConfigureAwait(false)inAsyncMethod. This meansAsyncMethodwill haveto be refactored to replace UI-dependent logic withIProgress<T>or other nonblocking callbacks. - Install a nested message loop. This should work but you'll have to think through all the re-entrancy ramifications.
- 阻止
Task使用Wait或Result。您可以使用in避免我博客中描述的死锁问题。这意味着将有被重构以取代UI依赖性逻辑或其他非阻塞回调。ConfigureAwait(false)AsyncMethodAsyncMethodIProgress<T> - 安装嵌套消息循环。这应该可行,但您必须考虑所有重入后果。
Personally, I think (1) is cleaner.
就个人而言,我认为(1)更简洁。
回答by Jarek Kardas
do this:
做这个:
var are = new AutoResetEvent(true);
sender.Dispatcher.Invoke(DispatcherPriority.Normal,
new Action(async () =>
{
try { ... } catch { ... } finally { are.Set(); }
}));
are.WaitOne();
return result;
are.WaitOne() will tell AutoResetEvent to wait until it is signaled which will happen when dispatcher action reaches finally block where are.Set() is called.
are.WaitOne() 将告诉 AutoResetEvent 等待直到发出信号,这将在调度程序操作到达调用 are.Set() 的 finally 块时发生。
回答by Demodave
This solved it for me.
这为我解决了它。
Add Task delay for invoker to complete.
public string SyncMethod(object parameter)
{
var sender = CurrentPage as MainView; // interop
var result = string.Empty;
if (sender != null)
{
await sender.Dispatcher.Invoke(DispatcherPriority.Normal,
new Func<Task>(async () =>
{
try
{
result = await sender.AsyncMethod(parameter.ToString());
}
catch (Exception exception)
{
result = exception.Message;
}
}));
}
await Task.Delay(200); // <-- Here
return result;
}

