【问题标题】:C#-WPF Strange async behaviorC#-WPF 奇怪的异步行为
【发布时间】:2015-12-29 04:18:40
【问题描述】:

我的 WPF 窗口有一个奇怪的行为。总而言之,我有一个 WPF 窗口,它对加载的事件执行异步操作

if (AppContext.OnlineMode)
   Task.Run(() => SynchronizeMails());

这个函数(synchronizeMails),做了很多事情(异步联系webservice,插入数据库,刷新GUI,......),第一次启动时,它需要很长时间。

我有一个按钮,允许用户断开连接,绑定到根据当前状态显示消息框的命令。就我而言,synchronizeMails 将 bool 设置为 true,以防止多次同步并防止在处理期间退出。我的命令实现查看这个布尔值,如果当前正在同步,则显示一个消息框。

ExitCommand = new RelayCommand(p => Task.Run(() =>
{
   RestartAsked = true;
   if (Synchronizing)
   {
      OpeningView.ShowWarning("...");
   }
});

出于风格原因,我们在自己的实现中重新编码了消息框,因此对 messageBox ShowWarning 的调用只是主 GUI 上的一个 ShowDialog,没有异步/等待的东西。

奇怪的是:当用户在此消息框上单击“确定”时,我的异步方法停止工作,执行 finaly 块(整个方法在 try-finally 块中以禁用我的布尔值,如果有一些错误) ,当然,这项工作还没有完成。

我不明白为什么带有 return 的 showDialog 让我的异步方法停止...

有人有想法吗?

【问题讨论】:

  • 您的 SyncronizeMails 方法中是否设置了布尔值?在调用方法之前尝试将 bool 设置为 true。
  • 是的,但是移动它不会改变任何东西
  • 您的 SynchronizeMails 方法中似乎抛出了一些异常。尝试将 catch(){;} 放在 finally 块之前,可能会发生一些 InvalidOperation 或 SynchronizeContext 的事情。
  • 这是我的第一个想法,但不,没有抛出异常。我在方法末尾的最后一条指令上放了一个断点,单击“确定”后,我的断点触发,而我在方法中的异步内容尚未完成...
  • 我发现这是因为我的异步内容位于 Parallel.For 和 Parallel.ForEach 中,这使它在结束之前返回...通过 for 更改 Parallel.For 和通过 foreach 更改 Parallel.ForEach让它工作。但是,我不知道如何并行化作业,因为它现在让它变慢了!

标签: c# .net wpf async-await relaycommand


【解决方案1】:

我发现这是因为我的异步内容位于 Parallel.For 和 Parallel.ForEach 中,导致它在结束之前返回

并行和异步是两种不同的并发形式,它们不能很好地结合在一起。

当您的算法受 CPU 限制且可从多核中受益时,应使用并行性。当您有 I/O 绑定或事件触发代码时,应使用异步。

异步联系网络服务,插入数据库,...

听起来异步是并发的适当形式,而不是并行。

正确应用异步,从“叶子”开始(即,执行 Web 服务调用的代码和插入数据库的代码),然后更改该代码以使用异步 API(使用 @ 987654321@)。由于您使用的是await,因此您必须将该方法更改为async,并将其返回类型更改为Task/Task<T>。那么该方法的所有调用者都需要使用await,变成async

最终,您将得到一个真正异步的正确 SynchronizeMailsAsync 方法,并且可以从您的加载事件处理程序中调用:

if (AppContext.OnlineMode)
  await SynchronizeMailsAsync();

请注意,Task.Run 不是必需的。 Task.Run 只能用于将 CPU 密集型工作推离 UI 线程,而您的工作是 I/O 密集型的。

现在,在您的代码中(未显示)您曾经拥有并行性的地方,您可以使用Task.WhenAll 使其并发。所以而不是:

Parallel.ForEach(sequence, x => MyAsync(x));

你应该这样做:

var tasks = sequence.Select(x => MyAsync(x));
await Task.WhenAll(tasks);

【讨论】:

  • 但是,我必须使用Task.Run,​​因为如果没有,会导致我在viewModel 中加载的方法不是异步的,我不能在其中等待?
  • @cdie: 如果它实际上是一个加载的事件,你应该可以只使用await 并将事件处理程序设置为async void
  • 这种方法异步不会有什么影响吗?
  • @cdie:我不确定你所说的“发病率”是什么意思。
  • 对不起,这是一个法语单词,意思是影响
【解决方案2】:

要检测在Task.Run 期间您的Task 对象中是否有未处理的异常被抛出,您应该处理TaskScheduler.UnobservedTaskException 事件。

如果Task 中包含的代码的其他部分出现异常,这将识别它们及其来源。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-06-30
    • 2017-05-13
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2012-08-17
    • 1970-01-01
    • 2020-07-28
    相关资源
    最近更新 更多