向UI报告进度的最佳策略-回调应该如何发生?


11

有时,用户会启动一项扩展的技术操作,该操作需要一段时间才能执行。在这些情况下,通常最好显示某种进度条以及有关当前正在执行哪个任务的信息。

为了避免UI和逻辑层之间的紧密耦合,通常最好通过某种代理进行通信。也就是说,后端不应该操纵自己的UI元素,甚至不应该直接与中介层进行交互。

显然,必须在某处进行一些回调才能使此工作正常进行。我通常以两种方式之一实现它:

  1. 将可变对象传递给后端,并让后端在进行时对其进行更改。发生更改时,对象会通知前端。

  2. 传递形式void f(ProgressObject)或后端调用的回调函数ProgressObject -> unit。在这种情况下,后端构造了ProgressObject,并且完全是被动的。我认为,每次要报告进度时,它都必须构造一个新对象。

这些方法的缺点和优点是什么?有没有商定的最佳使用方法?有不同的使用环境吗?

是否有完全不同的报告进度的技术被我忽略了?


1
关于可变与不可变,优点和缺点与其他地方相同。关于进度对象,这可能很轻;它可以像一个数字一样简单:一个百分比。
罗伯特·哈维

@RobertHarvey进度对象的大小通常取决于UI要求。例如,查看Windows复制对话框。我想这需要很多信息。
GregRos 2015年

1
@RobertHarvey对我来说是个新闻。它是什么?
GregRos

1
我会咬 我们使用BackgroundWorkerRH提及的内容。与“进度表”等一起包装在自定义类中,并提供了一种用于传达异常的简单机制-按照BackgroundWorker设计,该机制在单独的线程中运行。在某种程度上,我们以.Net建议的方式使用其功能,因此可以说是惯用的。并且在任何给定的语言/框架上下文中,“惯用”可能是最好的。
radarbob

2
我看不到您的两种方法之间的任何显着差异。从前端传递到后端的对象,该对象提供导致前端通知的方法,实际上具有回调的功能。并且,如果您的第二种方法使用或多或少复杂的参数对象来传递信息,或者如果它使用一些简单的值,则从体系结构的角度来看并没有什么不同。在这两种方法中,后端都会主动通知前端,不同之处只是次要细节,因此这里没有描述不同的概念。
布朗

Answers:


8

将可变对象传递给后端,并让后端在进行时对其进行更改。发生更改时,对象会通知前端。

如果后端在这方面进行通知,则很难平衡效率。如果您不追求进度更新的顺利进行,则可能会不小心发现增加进度最终会使完成操作所需的时间增加一倍或三倍。

传递后端调用的形式为void f(ProgressObject)或ProgressObject-> unit的回调函数。在这种情况下,后端将构造ProgressObject,并且它是完全被动的。我认为,每次要报告进度时,它都必须构造一个新对象。

我在这里没有太大的区别。

是否有完全不同的报告进度的技术被我忽略了?

从前端以单独的线程进行轮询,后端以原子方式递增。轮询在这里很有意义,因为它是在有限的时间内完成的操作,并且前端拾取状态发生变化的可能性很高,尤其是在您希望达到柔滑流畅的进度条的情况下。如果您不喜欢从前端线程进行轮询的想法,则可以考虑条件变量,但是在那种情况下,您可能希望避免在每个单个进度条增量上进行通知。


2

这是推式拉式通知机制之间的区别。

如果您希望后端任务在后台/工作线程中执行,则可变对象(pull)将需要由UI反复轮询并同步。

回调(push)仅在实际更改时为UI创建工作。许多UI框架还具有可从工作线程调用的invokeOnUIThread,以使一段代码在UI线程上运行,因此您实际上可以进行更改,而不必担心与线程相关的危害。(双关语意)

通常,推送通知是更可取的,因为它们仅在需要完成工作时才起作用。


1
我认为您的发言总体上是正确的。但是,对于这种特殊情况,进度条可能会迅速发生变化。如果您期望“进度”可能每秒变化很多次,则使用pull模型更有意义,因为否则您将担心UI收到太多通知以致无法处理。

发送进度对象可能会使后端使用的通知机制变得晦涩,因为进度对象可能正在进行回调。就我所记得,我实际上从未使用过拉动机制,我有点忘了它:P
GregRos

The mutable object (the pull) will need to be repeatably polled by the UI and synchronized if you expect the back-end task to be executed in a background/worker thread.-如果可变对象是对话框本身或对话框的工作接口,则不是。当然,无论如何,这相当于回调。
罗伯特·哈维

1
??OP清楚地描述了两种不同形式的推动机制,无需轮询。
布朗

0

我在AngularJS中使用websockets。当前端接收到一条消息时,它将在指定的消息区域中显示该消息,并在几秒钟后变为空白。在后端,我只是将状态消息发布到消息队列中。我只发送文本,但是没有理由不能发送状态百分比值或传输速度之类的状态对象。


0

您提到您的“两种方式”就好像它们是单独的概念一样,但是我想对此稍作介绍。

  1. 将可变对象传递给后端,并让后端在进行时对其进行更改。发生更改时,对象会通知前端。

您已经说过要避免UI和逻辑的紧密耦合,因此我可以放心地假设您传递的这个“可变对象”实际上是在逻辑模块中定义的特定接口的实现。这样,这仅仅是将回调传递到进程的另一种方式,该进程会定期调用有关进度的信息。

至于利弊...

方法(1)的缺点是,实现接口的类只能执行一次。(如果要使用不同的调用执行不同的作业,则需要switch语句或访问者模式。)使用方法(2),同一对象可以对后端代码的每次调用使用不同的回调,而无需使用开关。

方法(1)的优势在于,在接口上具有多个方法比处理方法(2)的多个回调或使用带有针对多个上下文的switch语句的单个回调要容易得多。


-2

您可以使用的技术可能非常不同。

我试图找出不同的情况

  • 请求数据库
  • 下载文件

对db的简单登录请求(平均db用一个elemt响应)不需要报告进度,但是可以在单独的任务ex中触发UI线程。异步或后台工作人员,这里您只需要一个回调即可获得结果。

但是,如果您查询查看包含100万商品的所有库存该怎么办?该查询需要几分钟的时间才能完成,因此在这种情况下,您需要以一个或多个表单的形式在业务逻辑中实现perport进度,然后可以更新UI并提供取消回调选项。

下载文件的情况相同。您始终可以在此处以字节形式的字节形式实现进度回调,并保持对HTTP的所有通信控制是非常常见的模式。

在我个人的方法上,我仅对客户实施业务进度逻辑,避免与端点共享其他对象。


1
这并不能真正回答有关优势/劣势的问题。
本尼
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.