【问题标题】:Maintaining a time gap with post vs postDelayed与 post 与 postDelayed 保持时间间隔
【发布时间】:2020-07-09 00:53:39
【问题描述】:

我需要以最小的时间间隔运行两个函数。由于超出此问题范围的原因,起初我试图控制在 webview 中运行的进程的时间(通过JavascriptInterface):

webView.post(() -> functionA());
// ... wait 2 secs in javascript and then...
webView.post(() -> functionB());

虽然这在大多数情况下运行良好,但对于一位用户而言,这两个函数有时似乎会立即彼此运行(仍然以正确的顺序运行,但只是没有时间间隔)。

回想起来,这是可以理解的,如果将可运行对象发布到处理程序只会将它们放入队列中,而不能保证根据它们被放入队列的时间来保持相对时间。

所以,如果是这样的话,那么我的新策略是忘记从webview中运行的javascript控制时间,而直接在Java中控制它。

所以问题是...使用postDelayed() 是否保证至少两个函数之间的最小时间间隔正在运行?

webView.post(() -> functionA());
webView.postDelayed(() -> functionB(), 2000);

我觉得它应该有预期的效果,但我很谨慎,它可能与我正在做的一样......在functionA 2 秒后将functionB 放入队列中,但不能保证他们实际上会保持他们之间的时间间隔

【问题讨论】:

  • 我使用过 PostDelayed,是的,它确实有效。
  • 你不能等到functionA返回一些东西或被执行(通过设置一些标志)然后才开始等待两秒钟。甚至将 functionB 延迟放入 functionA 中
  • 这是一个 WebView/JavaScript 问题还是 Java 问题?
  • 您是在寻找functionA() 返回后或functionA() 开始执行后的 2 秒间隔吗?

标签: java android asynchronous android-handler android-looper


【解决方案1】:

post()postDelayed() 正在做的是,他们将 Message 添加到 MessageQueue 中,Looper 会循环。所以post()里面的action不会同步执行,而是会在稍后的时间点执行。

webView.post(() -> functionA()) 将导致添加一个动作,该动作将在未来某个时间运行,此时webView 将通过其measure-layout-draw 方法。为了举例,我们假设这需要 15 毫秒。

webView.postDelayed(() -> functionB(), 2000) 将导致添加一个动作,从现在开始将在大约 2 秒内运行。所以,事实上,functionAfunctionB 不能保证在 2000 毫秒间隔内被调用(而且很可能不会),因为 functionA() 是在 "now + 15 处执行的"functionB() 是在"now + 2000"处执行的。

相反,如果您有这样严格的要求,也许您应该使用postAtTime() 重载之一而不是使用postDelayed()

考虑一种方法:执行webView.post(() -> functionA()) 并在functionA() 内部安排一个新的runnable,以便在2000 年代执行,例如Handler().postDelayed(2000, someRunnable)。我认为这对于您的用例来说可能是一种可行的方法。

【讨论】:

  • 所以如果functionA()的执行真的延迟了,functionA()仍然有可能在“now + 1999”处执行而functionB()在“now + 2000”处执行?理论上?是的,我现在实际上正在使用您建议的方法,即从functionA() 中调用functionB()...我仍然很想知道这个问题的答案。
  • 如果MessageQueuefunctionA()之前是满的,在functionA()之后是空的,那么理论上是的,这是可能的(这个不确定,只是根据原理进行推论这些组件是如何工作的)。老实说,我看不出这是怎么可能的。
【解决方案2】:

不确定您是要问 Java 问题还是 JavaScript 问题。

Java Executors 框架

如果是 Java,解决方案很简单。 Java 为在线程上运行任务提供了 Executors 框架。这包括安排任务在初始延迟后运行。 (请参阅 Oracle 的 tutorial。)

要让一对任务之间有时间间隔运行,请安排第一个任务立即运行。然后,在同一个调度的执行器服务上,调度第二个任务在延迟后运行。

示例

将您的任务定义为RunnableCallable 对象。

Runnable task = 
    () -> { 
        System.out.println( "Task is running. " + Instant.now() ); 
    }
;

使用Executors 实用程序类获取single-threaded scheduled executor service

ScheduledExecutorService ses = Executors.newSingleThreadScheduledExecutor() ;

安排您的每项任务。

在本例中,我们立即在后台线程上启动第一个任务。

ses.schedule( task1 , 0 , TimeUnit.SECONDS ) ;  // Runs immediately, in the background on another thread. 

第二个任务将在大约 45 秒后开始,也在后台线程上。

ses.schedule(               // Tell the executor what you want to run, and when you want it run on your behalf in a background thread.
    task2 ,                 // Define task to be executed as a `Runnable`.
    45 ,                    // Count of time to wait.
    TimeUnit.SECONDS        // Specify the granularity of time used in previous argument.
) ; 

捕获异常

提示:请务必使用通用的 try-catch 将工作包装在您的任务中。如果任何未捕获的异常(或错误)一直冒泡到执行器服务,则服务会停止。任何未来的计划任务都不会运行。这无声无息地发生。有关详细信息,请参阅my Answer 的相关问题。

关闭你的线程池

当您的所有任务都完成后,或者当您的应用程序终止时,通过调用其shutdown 方法之一来关闭计划的执行器服务。否则,支持您的执行程序服务的线程可能会无限期地继续运行。

Jakarta Concurrency 实用程序

如果您正在构建 Web 应用,您的 Jakarta EE 应用服务器可能支持 Jakarta Concurrency 以进一步简化此编码并自动关闭您的执行器服务的后台线程池。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-03-03
    • 1970-01-01
    • 2022-11-03
    • 2020-09-24
    • 2014-11-06
    • 2021-03-02
    相关资源
    最近更新 更多