异步编程变得更加自然。
C# 现在支持 async/await。
所有这些都允许我们编写响应应用程序,正如Anders Hejlsberg在这里所说的那样。我们的团队也喜欢异步。但是为了方便起见,我们必须开发一些帮助程序方法,因为我们为 .net v.4.0
编写代码
但是我们使用异步操作的次数越多,它就越困难。
举个例子:
我们有 GUI 的大纲细节部分。当用户单击某些主条目时,相当长的操作开始获取详细信息。此操作本质上可以是不可阻挡的,例如数据库请求或文件下载。因此,我们异步启动此长操作,以使 UI 保持响应。
现在我们必须决定如何处理主条目列表。
我们可以允许用户更改项目并开始新操作。已经运行的操作的结果现在可以放弃或缓存。
现在让我们想象一下,用户再次单击 item1、item2,然后再次单击 item1。当他相当快地执行此操作时,item1 的已启动且未取消的操作仍然无法完成。在这种情况下,我们最好等待此操作的结果,而不是开始新的操作。因此,必须存在当前正在执行的操作的某些缓存。(不知道如何使用 await 轻松完成此操作)
当然,我们可以禁用主项目列表直到操作完成,但尽管 UI 的其他部分保持活动状态,但不是很用户友好。
第二个示例(升级到第一个示例):
GUI 的详细信息部分由两个列表组成。每个列表的内容取决于所选的主条目。除此之外,列表还会相互影响,一个列表中的选择会更改另一个列表中的状态。(是的,相当复杂的用例)。
现在,如果我们希望异步获取两个列表的数据怎么办?
我们事先不知道哪个列表赢得了比赛并首先获得其数据。这真的不重要。重要的是赢家存在的事实。列表 1 已完成,用户更改了选择...但等待此选择应该会影响尚未填充的列表二的状态。同样,我们可以在加载两个列表之前禁止选择,但用户可以对列表执行许多独立的操作。因此,这不是一个选择。
好的,我们该怎么做?好吧,我们可以介绍在加载两个列表时要启动的任务。在此任务中,我们可以根据需要获取当前选择并设置状态。
有什么意义呢?
我已经阅读了有关并行编程的一般内容,但我相信 GUI 是一个非常保密的情况,因为用户交互使 GUI 随机更改其状态(从执行流的角度来看)。由于我们有GUI相关的设计模式(MVC,MVP,MVVM),我们必须有特殊的并行模式。
这是我的问题:
是否存在用于添加常见异步 GUI 任务的专用并行父项?
附言如果你认为这个问题更适合程序员.stackexchange,请自由迁移它。谢谢。
我发现在将桌面程序转换为异步程序时重新考虑 UI 的情况并不少见。异步程序可以比相应的同步程序具有更多可能的状态。
有几种有用的模式:禁用控件(或者,等效地,用"正在加载..."覆盖它们。sign),并保持状态"上下文"(我将在下面解释)。您也可以维护操作队列,但大多数人不会打扰,因为构建用于管理操作队列的 UI 并不容易。
此操作本质上可以是不可阻挡的,例如数据库请求或文件下载。
(旁注:这些都不是天生不可阻挡的)
我们可以允许用户更改项目并开始新操作。已经运行的操作的结果现在可以放弃或缓存。
这些都是合理的方法,具体取决于您的应用程序和操作类型。
现在让我们想象一下,用户再次单击 item1、item2,然后再次单击 item1。当他相当快地执行此操作时,item1 的已启动且未取消的操作仍然无法完成。在这种情况下,我们最好等待此操作的结果,而不是开始新的操作。因此,必须存在当前正在执行的操作的某些缓存。
我不建议针对病态用户场景进行设计。在这种情况下,如果用户真的以惊人的速度点击该主列表,那么让他们忍受性能下降。如果您真的担心它,您可以缓存结果(如上所述),以便 item1 的重新执行速度非常快。结果的缓存应该足够;您不需要执行操作的缓存。
当然,我们可以禁用主项目列表直到操作完成,但尽管 UI 的其他部分保持活动状态,但不是很用户友好。
要记住的一件事是,它对用户的不友好程度不亚于同步应用程序,同步应用程序(大概)在此期间没有响应。
每个列表的内容取决于所选的主条目。除此之外,列表还会相互影响,一个列表中的选择会更改另一个列表中的状态。
您可能需要重新考虑您的 UI。你真的需要一个这么复杂的吗?换一种方式思考:您是否使用任何公开可用的应用程序具有如此复杂的 UI?他们做什么呢?
也就是说,我在博客上解释了一个古老的异步编程技巧:异步回调上下文。从本质上讲,这个想法是您使用"cookie"来定义"当前"状态(对于某些范围);当状态发生变化时,您更改"cookie"。属于该范围的所有异步方法都可以监视 Cookie,并在 Cookie 发生更改时采取特殊操作。
您可以将object
用于cookie(如我在我的博客中所述),但您也可以使用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 中隐含的"状态"敏感的每个操作,它们在每次await
后检查 cookie 是否已更改(即,CancellationToken
已被取消)并采取适当的行为(在这种情况下,只是不过滤其他列表)。