从Dispatcher上调用的异步Action返回值

时间:2012-10-22 13:34:14

标签: c# wpf asynchronous task-parallel-library dispatcher

我正在开发一个WPF XBAP应用程序,它通过使用BrowserInteropHelper通过JavaScript为用户提供API。在以新的异步等待方式重写托管部分之后,需要等到异步操作完成而不使调用方法异步任务本身。我有:

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

此方法返回string.Empty,因为这在执行后立即发生。我试图将此操作分配给DispatcherOperation并执行while(dispatcherOperation.Status!=已完成){Wait(); ,但即使在执行后,该值仍为空。 dispatcherOperation.Task.Wait()或dispatcherOperation.Task.ContinueWith()也没有帮助。
修改
也尝试了

var op = sender.Dispatcher.BeginInvoke(...);
op.Completed += (s, e) => { var t = result; };

但是t也是空的,因为赋值在等待异步方法之前发生。
编辑
长话短说,SyncMethod JavaScript互操作处理程序帮助程序包装器,它运行的所有操作必须在Dispatcher上。 AsyncMethod是异步任务< T>并且在内部应用程序中执行和等待时工作完美,即在某个按钮处理程序中。它有很多依赖于UI的逻辑(显示状态进度条,更改光标等) 的 修改
AsyncMethod:

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

3 个答案:

答案 0 :(得分:2)

  

在以新的async-await方式重写托管部分之后,需要等到异步操作完成而不使调用方法异步任务本身。

这是你可以尝试做的最困难的事情之一。

  

AsyncMethod是异步任务&lt; T>并且在内部应用程序中执行和等待时工作完美,即在某个按钮处理程序中。它有很多与UI相关的逻辑

这种情况几乎不可能。


问题是您需要阻止在UI线程上运行的SyncMethod,同时允许AsyncMethod分派到UI消息队列。

您可以采取以下几种方法:

  1. 使用TaskWait阻止Result。您可以在ConfigureAwait(false)中使用AsyncMethod来避开deadlock problem described on my blog。这意味着AsyncMethod 重构为使用IProgress<T>或其他非阻塞回调替换与UI相关的逻辑。
  2. 安装嵌套的消息循环。这应该有效,但你必须仔细考虑所有的重新分歧。
  3. 就个人而言,我认为(1)更清洁。

答案 1 :(得分:0)

这样做:

var are = new AutoResetEvent(true);
sender.Dispatcher.Invoke(DispatcherPriority.Normal,                                      
        new Action(async () =>
        {
           try { ... } catch { ... } finally { are.Set(); }
        }));



are.WaitOne();
return result;

are.WaitOne()将告诉AutoResetEvent等待它发出信号,这将在调度程序操作到达finally块时发生,其中调用is.Set()。

答案 2 :(得分:-1)

这为我解决了。

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