C#5异步重入的解决方案


16

因此,关于C#5中新的异步支持的问题一直困扰着我:

用户按下一个按钮即可启动异步操作。呼叫立即返回,并且消息泵再次开始运行-这就是重点。

因此,用户可以再次按下按钮-导致重新进入。如果有问题怎么办?

在我看到的演示中,他们在await通话前禁用了按钮,然后在通话后再次启用了该按钮。在我看来,这似乎是现实应用中非常脆弱的解决方案。

我们是否应该编写某种状态机来指定必须为给定的一组运行操作禁用哪些控件?或者,还有更好的方法?

我很想在操作过程中只显示一个模式对话框,但这有点像使用大锤。

有人有什么好主意吗?


编辑:

我认为禁用在操作运行时不应该使用的控件是脆弱的,因为我认为当您拥有带有许多控件的窗口时,它很快就会变得很复杂。我喜欢使事情简单,因为在初始编码和后续维护期间,它减少了错误的机会。

如果存在针对特定操作应禁用的控件集合,该怎么办?如果同时执行多个操作怎么办?


有什么脆弱的呢?它向用户指示正在处理某些内容。添加一些动态指示工作正在处理中,这对我来说似乎是非常好的UI。
史蒂文·杰里斯

PS:.NET 4.5的C#称为C#5吗?
史蒂文·杰里斯

1
只是好奇为什么这个问题在这里而不是?这绝对是关于代码,因此它会属于那里我想......
C.罗斯

@ C.Ross,这更多是设计/ UI问题。这里并没有真正需要解释的技术要点。
Morgan Herlocker 2011年

在ajax HTML表单中,我们遇到了类似的问题,即用户点击了“提交”按钮,发送了ajax请求,然后我们希望阻止用户再次点击“提交”,在这种情况下,禁用按钮是非常常见的解决方案
Raynos,

Answers:


14

因此,关于C#5中新的异步支持的问题一直困扰着我:用户按下一个按钮即可启动异步操作。呼叫立即返回,并且消息泵再次开始运行-这就是重点。因此,用户可以再次按下按钮-导致重新进入。如果有问题怎么办?

让我们首先指出,即使没有该语言的异步支持,这已经是一个问题。在处理事件时,很多事情都可能导致消息出队。您已经必须为这种情况进行防御性编码。新的语言功能使这一点变得更加明显,那就是善良。

在导致不必要的重新进入的事件期间运行的消息循环的最常见来源是DoEvents。与之await相对的一件好事DoEvents是,await它使新任务更有可能不会“饿死”当前正在运行的任务。DoEvents抽送消息循环,然后同步调用可重入事件处理程序,而await通常新任务排入队列,以便它将在将来的某个时刻运行。

在我看到的演示中,他们在等待调用之前禁用了按钮,然后在之后再次启用了按钮。我认为禁用在操作运行时不应该使用的控件是脆弱的,因为我认为当您拥有带有许多控件的窗口时,它很快就会变得很复杂。如果存在针对特定操作应禁用的控件集合,该怎么办?如果同时执行多个操作怎么办?

您可以以与使用OO语言管理任何其他复杂性相同的方式来管理该复杂性:将机制抽象为一个类,并使该类负责正确实现策略。(尝试使这些机制在逻辑上与策略保持不同;应该可以在不对机制进行过多修改的情况下更改策略。)

如果您的表单上有很多控件,它们之间以有趣的方式交互,那么您将生活在一个复杂的世界中。您必须编写代码来管理这种复杂性。如果您不喜欢那样的话,请简化表格,以免复杂。

我很想在操作过程中只显示一个模式对话框,但这有点像使用大锤。

用户会讨厌的。


谢谢Eric,我想这是绝对的答案-这取决于应用程序开发人员。
Nicholas Butler

6

您为什么认为await在通话之前禁用按钮然后在通话结束后重新启用按钮比较脆弱?是因为您担心该按钮将永远不会重新启用吗?

好吧,如果该呼叫没有返回,则您无法再次进行该呼叫,因此这似乎是您想要的行为。这表示您应修复的错误状态。如果您的异步调用超时,那么这仍然会使您的应用程序处于不确定的状态-再次启用按钮可能很危险。

唯一可能出现混乱的情况是,是否有多个操作会影响按钮的状态,但是我认为这些情况很少见。


当然,您必须处理错误-我已经更新了我的问题
Nicholas Butler

5

我不确定这是否适用于您,因为我通常使用WPF,但是我看到您引用了a,ViewModel所以可能会这样。

代替禁用按钮,将IsLoading标志设置为true。然后,您要禁用的任何UI元素都可以绑定到该标志。最终结果是,它成为UI的工作来整理自己的启用状态,而不是您的业务逻辑。

除此之外,WPF中的大多数按钮都绑定到ICommand,通常我将ICommand.CanExecuteequal 设置为!IsLoading,因此当IsLoading等于true时,它会自动阻止Command执行(也为我禁用了该按钮)


3

在我看到的演示中,他们在等待调用之前禁用了按钮,然后在之后再次启用了按钮。在我看来,这似乎是现实应用中非常脆弱的解决方案。

这没有什么脆弱的,这正是用户所期望的。如果用户需要在再次按下按钮之前等待,请让他们等待。

我可以看到某些控件场景如何使在任何时候决定要禁用/启用的控件非常复杂,但是是否要管理一个复杂的UI会让人感到惊讶?如果某个特定控件有太多可怕的副作用,则始终可以禁用整个表单,然后在整个过程中抛出“加载”状态。如果每个单独的控件都是这种情况,那么您的UI可能一开始就没有很好的设计(紧密耦合,不良的控件分组等)。


谢谢-但是如何管理复杂性?
尼古拉斯·巴特勒

一种选择是将相关控件放入容器中。禁用/启用控件容器,而不是控件。即:EditorControls.Foreach(c => c.Enabled = false);
Morgan Herlocker 2011年

2

禁用小部件是最简单且容易出错的选项。您可以在开始异步操作之前在处理程序中禁用它,然后在操作结束时重新启用它。因此,将每个控件的disable + enable保留在该控件的处理本地。

您甚至可以创建一个包装器,该包装器将使用委托和控件(或其中的更多控件),禁用控件并异步运行某些东西,该操作将完成委托并重新启用控件。它应该注意异常(最后要启用),因此,如果操作失败,它仍然可以可靠地工作。您可以将其与在状态栏中添加消息相结合,以便用户知道他在等待什么。

您可能需要添加一些逻辑来计算禁用次数,因此,如果您有两个操作,则系统将继续工作,这两个操作都应禁用第三个操作,但不能互斥。您将控件禁用了两次,因此只有再次启用它才能再次启用。那应该涵盖大多数情况,同时使情况尽可能地分开。

另一方面,像状态机之类的东西都会变得复杂。以我的经验,我见过的所有状态机都很难维护,而您的状态机将需要包含整个对话框,将所有内容捆绑在一起。


谢谢-我喜欢这个主意。那么,您的窗口是否会有一个管理器对象,可以计算每个控件的禁用和启用请求的数量?
Nicholas Butler

@NickButler:嗯,您已经为每个窗口(窗体)都有一个管理器对象。因此,我将有一个实现请求和计数的类,然后将实例添加到相关表单中。
Jan Hudec

1

尽管Jan Hudec和Rachel表达了相关想法,但我还是建议使用类似Model-View的架构-在对象中表达表单的状态并将表单元素绑定到该状态。这将以一种可以扩展到更复杂场景的方式禁用该按钮。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.