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