[英]Prevent multiple asynchronous calls from being in flight simultaneously without blocking
Here's essentially my problem: 这基本上是我的问题:
while (true) {
if (previous 'doWorkAsync' method is not still in flight) {
doWorkAsync() // this returns immediately
}
wait set amount of time
}
A couple solutions come to mind for me: 我想到了几种解决方案:
Block until doWorkAsync() completes. 阻止,直到doWorkAsync()完成。 This is not desirable to me for a few reasons.
出于某些原因,这对我来说是不可取的。 It (potentially) results in waiting longer than I really needed to in the 'wait some set amount of time' line (eg if doWorkAsync takes 5 seconds, and the set amount of waiting time is 10 seconds, this will result in 15 seconds of waiting between calls, which isn't what I wanted).
它(可能)导致在“等待一定时间”行中等待的时间超过我真正需要的时间(例如,如果doWorkAsync需要5秒,并且设定的等待时间为10秒,这将导致15秒在电话之间等待,这不是我想要的)。 Of course, I could account for this by waiting less time, but somehow it just feels clunky.
当然,我可以通过等待更少的时间来解释这一点,但不知何故它只是感觉笨重。 It also ties up this thread unnecessarily.
它还不必要地绑定了这个线程。 Instead of waiting for this task to come back, this thread could handle other work, like making config updates so the next call to doWorkAsync() has fresh data.
该线程可以处理其他工作,而不是等待此任务返回,例如进行配置更新,以便下一次调用doWorkAsync()具有新数据。
Use a gating mechanism. 使用门控机制。 The easiest implementation that comes to mind is a boolean, set before calls to doWorkAsync(), and unset when doWorkAsync() completes.
想到的最简单的实现是布尔值,在调用doWorkAsync()之前设置,在doWorkAsync()完成时取消设置。 This is essentially what I'm doing now, but I'm not sure if it's an anti-pattern??
这基本上就是我现在所做的,但我不确定它是否是反模式?
Is #2 the right way to go, or are there better ways to solve this problem? #2是正确的方法,还是有更好的方法来解决这个问题?
EDIT : If it helps, doWorkAsync() returns a ListenableFuture (of guava). 编辑 :如果有帮助,doWorkAsync()返回一个ListenableFuture(番石榴)。
The original question may not have been 100% clear. 最初的问题可能不是100%明确的。 Here's the crux.
这是关键。 If the async request finishes before the given timeout, this code will always work.
如果异步请求在给定的超时之前完成,则此代码将始终有效。 However, if the async task takes SET_AMOUNT_OF_TIME + epsilon to complete, then this code will sleep twice as long as necessary, which is what I'm trying to avoid.
但是,如果异步任务需要SET_AMOUNT_OF_TIME + epsilon完成,那么此代码将根据需要休眠两倍,这正是我要避免的。
The simplest way to do this is using the wait
and notifyAll
methods already in Java. 最简单的方法是使用Java中已有的
wait
和notifyAll
方法。 All you need to do is use an AtomicBoolean
as a flag and block on it until the another Thread
tells you something has changed. 您需要做的就是使用
AtomicBoolean
作为标志并阻止它,直到另一个Thread
告诉您某些内容已更改。
The difference between that and your approach is that a blocked thread doesn't do anything whereas a polling thread uses CPU time. 它与您的方法之间的区别在于,阻塞的线程不执行任何操作,而轮询线程使用CPU时间。
Here is a simple example using two Thread
s - the Runnable
" First
" is submitted and it waits on done
until the Runnable
" Second
" notifies that it has changed the flag. 下面是一个使用两个
Thread
的简单示例 - 提交Runnable
“ First
”并等待done
直到Runnable
“ Second
”通知它已经更改了标志。
public class App {
private static final AtomicBoolean done = new AtomicBoolean(false);
private static final class First implements Runnable {
@Override
public void run() {
while (!done.get()) {
System.out.println("Waiting.");
synchronized (done) {
try {
done.wait();
} catch (InterruptedException ex) {
return;
}
}
}
System.out.println("Done!");
}
}
private static final class Second implements Runnable {
@Override
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException ex) {
return;
}
done.set(true);
synchronized (done) {
done.notifyAll();
}
}
}
public static void main(String[] args) throws InterruptedException {
final ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new First());
Thread.sleep(1000);
executorService.submit(new Second());
executorService.shutdown();
}
}
The sleep
calls are just to show that a task of arbitrary length can take place, obviously they are not required. sleep
调用只是为了表明可以进行任意长度的任务,显然它们不是必需的。
The thing to note is that First
prints "waiting" every time it enters the loop and, if you run the code, it only prints it once. 需要注意的是,
First
每次进入循环时都会打印“等待”,如果运行代码,它只打印一次。 The second thing to note is that First
reacts to the changing of the flag immediately as it is told to awake and recheck when the flag is changed. 需要注意的第二件事是,当标志被唤醒时,
First
会立即对标志的更改作出反应,并在标志更改时重新检查。
I have used return
in the InterruptedException
blocks, you may want to used Thread.currentThread().interrupt()
instead so that the process doesn't die if it's spuriously interrupted. 我在
InterruptedException
块中使用了return
,你可能想要使用Thread.currentThread().interrupt()
这样如果它被虚假中断,进程就不会死掉。
A more advanced approach is to use Lock
and Condition
更高级的方法是使用
Lock
和Condition
public class App {
private static final Lock lock = new ReentrantLock();
private static final Condition condition = lock.newCondition();
private static final class First implements Runnable {
@Override
public void run() {
lock.lock();
System.out.println("Waiting");
try {
condition.await();
} catch (InterruptedException ex) {
return;
} finally {
lock.unlock();
}
System.out.println("Done!");
}
}
private static final class Second implements Runnable {
@Override
public void run() {
lock.lock();
try {
Thread.sleep(1000);
condition.signalAll();
} catch (InterruptedException ex) {
return;
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
final ExecutorService executorService = Executors.newFixedThreadPool(2);
executorService.submit(new First());
Thread.sleep(1000);
executorService.submit(new Second());
executorService.shutdown();
}
}
In this situation First
acquires a lock on the Lock
object the immediately calls await
on the Condition
. 在这种情况下,
First
获取Lock
对象的Lock
,立即调用await
Condition
。 The releases the lock and blocks on the Condition
. 释放
Condition
上的锁和块。
Second
then acquires a lock on the Lock
and calls signalAll
on the Condition
which awakes First
. 然后
Second
获取Lock上的Lock
并在唤醒First
的Condition
上调用signalAll
。
First
then reacquires the lock and continues execution, printing "Done!". First
重新获取锁定并继续执行,打印“完成!”。
EDIT 编辑
The OP would like to call the method doWorkAsync
with a specified period, if the method takes less time than the period then the process has to wait. OP希望在指定的时间段内调用
doWorkAsync
方法,如果该方法花费的时间少于周期,则进程必须等待。 If the method takes longer then the method should be called again immediately after. 如果方法花费的时间更长,那么应该在之后立即再次调用该方法。
The task needs to be stopped after a certain time. 任务需要在一段时间后停止。
At no point should the method be running more than once simultaneously. 在任何时候,该方法都不应同时运行多次。
The easiest approach would be to call the method from a ScheduledExecutorService
, the Runnable
would wrap the method and call get
on the Future
- blocking the scheduled executor until it is done. 最简单的方法是从
ScheduledExecutorService
调用该方法, Runnable
将包装该方法并调用Future
上的get
- 阻塞调度的执行程序直到完成。
This guarantees that the method is called with at least WAIT_TIME_BETWEEN_CALLS_SECS
delay. 这保证了至少以
WAIT_TIME_BETWEEN_CALLS_SECS
延迟调用该方法。
Then schedule another task that kills the first one after a set time. 然后安排另一个任务,在一段时间后杀死第一个任务。
final ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
final Future<?> taskHandle = scheduledExecutorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
final ListenableFuture<Void> lf = doWorkAsync();
try {
doWorkAsync().get();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
} catch (ExecutionException ex) {
throw new RuntimeException(ex);
}
}
}, 0, WAIT_TIME_BETWEEN_CALLS_SECS, TimeUnit.SECONDS);
scheduledExecutorService.schedule(new Runnable() {
@Override
public void run() {
taskHandle.cancel(false);
}
}, TOTAL_TIME_SECS, TimeUnit.SECONDS);
The best solution would be call the raw Runnable
on a ScheduledExecutorService
rather than calling it on another executor and blocking on the ListenableFuture
. 最好的解决方案是在
ScheduledExecutorService
上调用原始Runnable
,而不是在另一个执行器上调用它,并在ListenableFuture
上ListenableFuture
。
Think what you are looking for is The Reactor Pattern . 想想你正在寻找的是反应堆模式 。
Is there a reason you don't want these things running at the same time? 您是否有理由不希望这些东西同时运行? If what you want to do is chain them, you could use Futures.
如果您想要做的是链接它们,您可以使用期货。 Akka has Composable Futures and mappable ones.
Akka拥有可组合期货和可映射期货。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.