C#-WPF 奇怪的异步行为

C#-WPF Strange async behavior

我的 WPF window 有一个奇怪的行为。总而言之,我有一个 WPF window 在加载的事件

上执行异步操作
if (AppContext.OnlineMode)
   Task.Run(() => SynchronizeMails());

这个函数 (synchronizeMails),做很多事情(异步联系网络服务,插入数据库,刷新 GUI,...),第一次启动时,需要一点时间。

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

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

出于样式原因,我们在自己的实现中重新编码了消息框,因此对 messageBox ShowWarning 的调用只是主 GUI 上的 ShowDialog,没有 async/await 内容。

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

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

有人知道吗?

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

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

I've found that it was because my async stuff were in Parallel.For and Parallel.ForEach, that make it return before end

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

当你有 CPU 绑定的算法可以从多核中获益时,应该使用并行性。当您有 I/O-bound 或事件触发代码时,应使用异步。

async contacting webservice, insert in database, ...

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

正确地应用异步,从"leaves"开始(即调用web服务的代码和插入数据库的代码),然后更改该代码使用异步 API(使用 await)。由于您使用的是 await,因此您必须将该方法更改为 async,并将其 return 类型更改为 Task/Task<T>。那么该方法的所有调用者都需要使用await,变成async,等等

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

if (AppContext.OnlineMode)
  await SynchronizeMailsAsync();

请注意 Task.Run 不是必需的。 Task.Run 应该只用于将 CPU 绑定的工作推离 UI 线程,而你的工作是 I/O-bound.

现在,在您的代码中(未显示)您曾经具有并行性的点,您可以使用 Task.WhenAll 使其并发。所以代替:

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

你应该做的:

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