【问题标题】:Returning value from async Action invoked on Dispatcher从 Dispatcher 上调用的异步操作返回值
【发布时间】:2018-11-12 23:07:18
【问题描述】:

我正在开发一个 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;        
}

这个方法返回 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; };

但是 t 也是空的,因为赋值发生在等待异步方法之前。
编辑
长话短说,SyncMethod JavaScript 互操作处理程序助手包装器,它运行的所有操作都必须在 Dispatcher 上。 AsyncMethod 是 async Task 并且在从应用程序内部(即在某个按钮处理程序内部)执行和等待时可以完美运行。它有很多与 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;    
}

【问题讨论】:

  • 在这里使用Dispatcher.Invoke 有什么意义?您没有触摸 UI 元素...

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


【解决方案1】:

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

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

AsyncMethod 是异步任务 并且在从应用程序内部(即在某个按钮处理程序内部)执行和等待时完美运行。它有很多依赖于 UI 的逻辑

这种情况几乎是不可能的。


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

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

  1. 使用WaitResult 阻止Task。您可以通过在AsyncMethod 中使用ConfigureAwait(false) 来避免deadlock problem described on my blog。这意味着 AsyncMethod必须进行重构,以将依赖于 UI 的逻辑替换为 IProgress&lt;T&gt; 或其他非阻塞回调。
  2. 安装嵌套消息循环。这应该可行,但您必须考虑所有重入的后果。

就个人而言,我认为 (1) 更干净。

【讨论】:

  • 正要根据 OP 的 cmets 重写我的答案以说出这样的话,尽管我没有想到选项 #2。
  • 试过ConfigureAwait(false),没有任何效果。通过 Result 阻止工作,但 UI 更新(实际上是 BusyIndi​​cator - 使用与此处所述相同的方法 - adamsteinert.com/blog/2011/10/busy-indicators-and-many-threads )现在不会发生。现在正在寻找这个问题的解决方案。感谢您的博客链接,将深入挖掘。
  • 你的AsyncMethod真的是异步的吗? ConfigureAwait(false) 没有效果的唯一原因是 awaited 的所有事情都已经完成或同步。
  • AsyncMethod 运行数据库操作(假设是多次插入)并返回新插入的行 ID(参见第一篇文章的第三次编辑)。我尝试对所有插入任务以及最后一个返回全局结果的任务执行 ConfigureAwait(false)。
  • @Demodave:您可以在此处查看不同方法的一些示例:msdn.microsoft.com/en-us/magazine/mt238404.aspx
【解决方案2】:

这样做:

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 等待,直到它发出信号,这将在调度程序操作到达调用 are.Set() 的 finally 块时发生。

【讨论】:

  • 这个问题想多了。您无需费力地等待任务完成;它提供了一个方法(实际上是两个)可以做到这一点。
  • AsyncMethod 在 WaitOne() 之后运行成功,但阻塞 UI 并且永不返回(仅应用程序关闭,返回空)。
  • 我的错,它应该是 var are = new AutoResetEvent(true); -> true 作为参数,不是 false;我更新了上面的代码。
  • 代码与原始代码的结果相同。还是谢谢。
【解决方案3】:

这为我解决了。

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-03-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多