BeginInvoke 调用的同步

Posted

技术标签:

【中文标题】BeginInvoke 调用的同步【英文标题】:Synchronization of BeginInvoke calls 【发布时间】:2018-04-25 02:07:17 【问题描述】:

所以我调用Dispatcher.BeginInvoke()Timer.Elapsed 事件中执行一些UI 操作。计时器滴答作响,BeginInvoke() 的多个新实例可能会在之前的调用完全处理之前堆积起来。在当前调用的处理完成后,我总是对只选择BeginInvoke() 的最新实例感兴趣,即消息队列中任何以前未处理的实例都应该被丢弃。

清空 Dispatcher 的 BeginInvoke 队列以实现此目的的正确方法是什么?

为了演示一个示例,假设我每秒多次从 Timer.Elapsed 事件中的传感器读取值,然后更新复杂的 UI 以显示读取的值。此 UI 更新操作需要一些时间,在此期间,读取值的一个或多个新实例会堆积在调度程序队列中以进行渲染。显然,当我从传感器获得更新的值时,我想丢弃等待队列中的所有实例,只保留当前实例,以便在处理器空闲时发送以进行渲染。

【问题讨论】:

正确的方法”?空无一人。你的问题太宽泛了,可能的答案太多了。到目前为止,您尝试过什么来实现这一目标? ... ... 例如,您是否尝试过设置一个代表队列,当队列为空时您只调用BeginInvoke()?或者甚至可能只是一个字段(因为您只关心最新的),仅当字段为 null 时才调用 BeginInvoke()?或者您可能已经尝试过,因为无论如何您都无法在计时器间隔内完成操作,使用更长的间隔?或者可能根本不使用计时器,而只是在每次操作结束时再次调用BeginInvoke()?或者,也许您应该有一个单独的 UI 更新计时器,以较慢的间隔复制传感器读数以进行 UI 显示 @PeterDuniho:在读取下一个值之前,我已经等不及操作完成了。计时器是一种通用时钟,它会以恒定的速率滴答作响。 @PeterDuniho:我也意识到这听起来有点宽泛。但我正在专门寻找一种清除调度程序队列的方法。 Timer.AutoReset 属性非常致命,将其初始化为 true 并不是一个好的设计决策。您必须自己将其显式设置为 false。在调用的代码中调用 Start() 以使计时器再次计时。请记住,当您所做的只是让代码在 UI 线程上运行时,使用此 Timer 并没有任何好处,请改用 DispatcherTimer。 【参考方案1】:

由于您没有管理 UI 线程,因此没有机会使回调出队。 但是你可以使用CancellationTokenSource:

    传递CancellationTokenSource (CancellationTokenSource.Token) 到调度程序和你的回调。

    通过在回调中重复调用 CancellationToken.ThrowIfCancellationRequested() 来监听取消,并捕获在调用 CancellationTokenSource.Cancel() 后将引发的 OperationCanceledException 异常

    使用 catch 块捕获 OperationCanceledException 并进行清理以将状态反转到执行回调之前的状态

    在使用新操作调用调度程序之前,您可以通过调用 CancellationTokenSource.Cancel() 取消所有以前的回调。这将触发 ThrowIfCancellationRequested() 在回调中实际抛出 OperationCanceledException

    使用新的 CancellationTokenSource 实例中的新回调和新 CancellationToken 调用调度程序,并处置所有已取消的 CancellationTokenSource 实例。

这样您可以取消调度程序操作,例如以防它长时间运行或阻止它执行以防操作仍在等待中。否则,您必须将下一个调度程序操作排入队列并覆盖上一个操作的更改。

Dispatcher.InvokeAsync(...) 等于 Dispatchher.BeginInvoke(...) 但此外它还允许您将取消令牌传递给调度程序.

【讨论】:

他说这个动作很耗时。因此,在继续在调度程序上产生新操作之前取消那些长时间运行的任务似乎更有效。为什么要花时间在你现在肯定会丢弃的东西上?干嘛要等?假设您在队列中有三个操作,每个操作需要 1 分钟才能执行。您必须等待三分钟才能获得有效值,因为只有最后一个有效。 Dispatcher 通过使用 CancellationToken 提供取消执行的基础设施。这对我来说很干净。 拥有一个单独的 DispatcherTimer 意味着根本不调用 BeginInvoke 这个问题太宽泛,无法回答。您正在解决一个很可能不存在的问题。

以上是关于BeginInvoke 调用的同步的主要内容,如果未能解决你的问题,请参考以下文章

委托的异步编程和同步编程的使用( Invoke 和BeginInvoke)

在创建窗口句柄之前,不能对控件调用 Invoke 或 BeginInvoke

在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke。

在创建窗口句柄之前,不能在控件上调用 Invoke 或 BeginInvoke

c# 从delegate.begininvoke 捕获异常而不调用delegate.endinvoke

跨线程调用控件 Invoke 与 BeginInvoke 区别