0

异步编程变得更加自然。
C# 现在支持异步/等待。
正如 Anders Hejlsberg 所说,所有这些都使我们能够编写响应式应用程序。我们的团队也喜欢异步。但是,当我们为 .net v.4.0 编写代码时,为了方便起见,我们必须开发一些辅助方法

但是我们使用异步操作的次数越多,它就变得越困难。
只是一个例子:

我们有 GUI 的主细节部分。当用户点击一些主项目相当长的操作开始获取详细信息。此操作本质上是不可阻挡的,例如数据库请求或文件下载。因此,我们异步启动这个长操作以使 UI 保持响应。
现在我们必须决定如何处理主项目列表。
我们可以允许用户更改项目并开始新的操作。已经运行的操作的结果现在可以放弃或放入缓存。
现在让我们想象一下,用户再次点击 item1、item2 和 item1。当他相当快地执行此操作时,item1 的已启动且未取消的操作仍可能无法完成。在这种情况下,我们最好等待这个操作的结果,而不是开始一个新的。因此,必须存在一些当前正在执行的操作的缓存。(不知道如何使用 await 轻松完成)
当然我们可以禁用主项目列表直到操作完成,但用户界面的其他部分仍然存在,但用户友好性不是很高。

第二个例子(对第一个的升级):
GUI的细节部分由两个列表组成。每个列表的内容取决于所选的主条目。除此之外,列表相互影响的方式是,一个列表中的选择会改变另一个列表的状态。(是的,相当复杂的用例)。
现在,如果我们希望异步获取两个列表的数据怎么办?
我们事先不知道哪个列表赢得比赛并首先获得其数据。这真的没关系。重要的是获胜者存在的事实。列表一已完成,用户更改了选择...但等待此选择会影响尚未填充的列表二的状态。同样,我们可以在加载两个列表之前禁止选择,但是用户可以对列表执行许多独立操作。因此,这不是一个选择。
好的,我们该怎么做呢?好吧,我们可以引入两个列表加载时要启动的任务。在这个任务中,我们可以获得当前的选择并根据需要设置状态。

重点是什么?我一般
都读过并行编程,但我认为 GUI 是一个非常特殊的情况,因为用户交互使 GUI 随机改变它的状态(从执行流程的角度来看)。由于我们有 gui 相关的设计模式(MVC、MVP、MVVM),我们必须有特殊的并行模式

这是我的问题:

是否存在专门的并行模式来解决常见的异步 GUI 任务?

PS如果您认为这个问题更适合programmers.stackexchange,请随意迁移它。谢谢你。

4

1 回答 1

1

我发现在将桌面程序转换为异步时重新考虑 UI 并不少见。与相应的同步程序相比,异步程序可以具有更多可能的状态。

有几个有用的模式:禁用控件(或者,等效地,用“正在加载...”符号覆盖它们),并保持状态“上下文”(我在下面解释)。您也可以维护一个操作队列,但大多数人不会打扰,因为构建一个用于管理操作队列的 UI 并不容易。

此操作本质上是不可阻挡的,例如数据库请求或文件下载。

(旁注:这些都不是天生不可阻挡的)

我们可以允许用户更改项目并开始新的操作。已经运行的操作的结果现在可以放弃或放入缓存。

这些都是合理的方法,具体取决于您的应用程序和操作类型。

现在让我们想象一下,用户再次点击 item1、item2 和 item1。当他相当快地执行此操作时,item1 的已启动且未取消的操作仍可能无法完成。在这种情况下,我们最好等待这个操作的结果,而不是开始一个新的。因此,必须存在一些当前正在执行的操作的缓存。

我不建议为病态的用户场景进行设计。在这种情况下,如果用户真的以惊人的速度点击该主列表,那么让他们忍受性能下降。如果你真的很在意,你可以缓存结果(就像你上面提到的那样),这样 item1 的重新执行就会非常快。结果的缓存应该足够了;您不需要执行操作的缓存。

当然,我们可以在操作完成之前禁用主项目列表,但是虽然 UI 的其他部分保持活动状态,但对用户来说并不是很友好。

要记住的一件事是,它与同步应用程序一样用户不友好,同步应用程序(大概)在这段时间内没有响应。

每个列表的内容取决于所选的主条目。除此之外,列表相互影响的方式是,一个列表中的选择会改变另一个列表的状态。

您可能需要重新考虑您的 UI。你真的需要这么复杂的吗?换一种方式想一想:您使用的任何公开可用的应用程序是否具有如此复杂的 UI?他们会做什么呢?

也就是说,我在博客中解释了一个古老的异步编程技巧:异步回调上下文。本质上,这个想法是您使用“cookie”来定义“当前”状态(对于某些范围);当状态改变时,你改变“cookie”。属于该范围的所有异步方法都可以监视 cookie 并在其更改时采取特殊措施。

您可以使用objectcookie(正如我在博客中所描述的那样),但您也可以使用CancellationToken

private CancellationTokenSource masterListSelectionCookie;
private Task list1Download;
private Task list2Download;

void MasterList_Click(...)
{
  // Change the cookie, canceling any previous one.
  if (masterListSelectionCookie != null)
    masterListSelectionCookie.Cancel();
  masterListSelectionCookie = new CancellationTokenSource();

  // Clear out both lists.
  list1.Items.Clear();
  list2.Items.Clear();

  // Start both lists downloading.
  list1Download = DownloadList1Async();
  list2Download = DownloadList2Async();
}

async void List1_Click(...)
{
  // Get a local copy of the current cookie.
  var localMasterListSelectionCookie = masterListSelectionCookie.Token;

  // Ensure list2 is done downloading.
  await list2Download;

  // If the cookie has changed, ignore the click.
  if (localMasterListSelectionCookie.IsCancellationRequested)
    return;

  // Apply the click changes to list2 items.
  FilterList2(list1.Item);
}

async void List2_Click(...)
{
  // Get a local copy of the current cookie.
  var localMasterListSelectionCookie = masterListSelectionCookie.Token;

  // Ensure list1 is done downloading.
  await list1Download;

  // If the cookie has changed, ignore the click.
  if (localMasterListSelectionCookie.IsCancellationRequested)
    return;

  // Apply the click changes to list1 items.
  FilterList1(list2.Item);
}

这个想法是,对于每个对 cookie 中隐含的“状态”敏感的操作,它们会在每个操作之后检查awaitcookie 是否已更改(CancellationToken即已被取消)并采取适当的行为(在这种情况下,只是不过滤其他列表)。

于 2013-01-22T00:50:20.230 回答