简体   繁体   English

Java:如何在满足特定条件时排队要执行的异步调用?

[英]Java: How can I queue up asynchronous calls to be executed when a certain condition is met?

TL;DR: I want to perform an asynchronous Call to a REST-API. TL;DR:我想对 REST-API 执行异步调用。 The standard call would give me a CompleteableFuture<Response>, however because the API has a limit on how many calls it allows in a certain amount of time I want to be able to queue up calls to 1. execute them in order and 2. execute them only when I am not exceeding the APIs limits at that current moment, otherwise wait.标准调用会给我一个 CompleteableFuture<Response>,但是因为 API 对它在一定时间内允许的调用数量有限制,我希望能够将调用排队到 1. 按顺序执行它们和 2。仅当我当时没有超过 API 限制时才执行它们,否则请等待。

Long verson:长版:

I am using Retrofit to perform Rest calls to an API and Retrofit returns a CompleteableFuture<WhateverResponseClassIDeclare> when I call it.我正在使用 Retrofit 对 API 执行 Rest 调用,当我调用它时,Retrofit 返回 CompleteableFuture<WhateverResponseClassIDeclare>。 However due to limitations of the API I am calling I want to have tight control over when and in what order my calls go out to it.但是,由于我调用的 API 的限制,我想严格控制调用的时间和顺序。 In detail, too many calls in a certain timeframe would cause me to get IP banned.具体来说,在某个时间段内调用太多会导致我被 IP 封禁。 Similarly I want to maintain the order of my calls, even if they won't get executed immediately.同样,我想保持我的调用顺序,即使它们不会立即执行。 The goal is to call a Wrapper of the API that returns a CompleteableFuture just like the original API but performs those in-between steps asynchronously.目标是调用 API 的 Wrapper,它像原始 API 一样返回 CompleteableFuture,但异步执行这些中间步骤。

I was playing around with BlockingQueues, Functions, Callables, Suppliers and everything inbetween, but I couldn't get it to work yet.我在玩 BlockingQueues、Functions、Callables、Suppliers 以及它们之间的所有东西,但我还不能让它工作。

Following there is my currently NON FUNCTIONAL code I created as a Mockup to test the concept.下面是我创建的当前非功能代码作为模型来测试这个概念。

    import java.util.concurrent.BlockingDeque;
    import java.util.concurrent.CompletableFuture;
    import java.util.concurrent.ExecutionException;
    import java.util.concurrent.LinkedBlockingDeque;
    import java.util.function.Function;

    public class Sandbox2 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {


        MockApi mockApi = new MockApi();

        CompletableFuture<Integer> result1 = mockApi.requestAThing("Req1");
        CompletableFuture<Integer> result2 = mockApi.requestAThing("Req2");
        CompletableFuture<Integer> result3 = mockApi.requestAThing("Req3");

        System.out.println("Result1: " + result1.get());
        System.out.println("Result2: " + result2.get());
        System.out.println("Result3: " + result3.get());

    }

    public static class MockApi {
        ActualApi actualApi = new ActualApi();

        BlockingDeque<Function<String, CompletableFuture<Integer>>> queueBlockingDeque = new LinkedBlockingDeque();

        public CompletableFuture<Integer> requestAThing(String req1) {

            Function<String, CompletableFuture<Integer>> function = new Function<String, CompletableFuture<Integer>>() {
                @Override
                public CompletableFuture<Integer> apply(String s) {
                    return actualApi.requestHandler(s);
                }
            };


            return CompletableFuture
                    .runAsync(() -> queueBlockingDeque.addLast(function))
                    .thenRun(() -> waitForTheRightMoment(1000))
                    .thenCombine(function)
        }

        private void waitForTheRightMoment(int time) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class ActualApi {

        public CompletableFuture<Integer> requestHandler(String request) {
            return CompletableFuture.supplyAsync(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return Integer.parseInt(request.substring(3));
            });
        }
    }
}

Pre JDK 9 (JDK 1.8) JDK 9 (JDK 1.8) 之前的版本

You can make use of ScheduledExecutor that accepts items to execute asynchronously on a pre-configured thread pool at a pre-fixed rate / delay.您可以使用ScheduledExecutor接受项目以预先固定的速率/延迟在预先配置的线程池上异步执行。

You can obtain such a service as follows:您可以通过以下方式获得此类服务:

private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

Once an instance of ScheduledExecutorService is created, you can start submitting items (requests) to be executed as follows:一旦创建了ScheduledExecutorService的实例,您就可以开始提交要执行的项目(请求),如下所示:

executorService.schedule(
    () -> actualApi.requestHandler(req),
    delay,
    unit
);

Meanwhile, using a direct call want lead a CompletableFuture<Integer> but instead would lead a ScheduledFuture<CompletableFuture<Integer>> on which you will have to block to get the wrapped result.同时,使用直接调用想要导致CompletableFuture<Integer>而是导致ScheduledFuture<CompletableFuture<Integer>> ,您将不得不阻止以获得包装结果。

Instead, you would need to block on your final requests results inside the ScheduledExecutorService then wrap your final request result in a completed ComppletableFuture :相反,您需要在ScheduledExecutorService阻止您的最终请求结果,然后将您的最终请求结果包装在一个完整的ComppletableFuture

public <T> CompletableFuture<T> scheduleCompletableFuture(
        final CompletableFuture<T> command,
        final long delay,
        final TimeUnit unit) {
    final CompletableFuture<T> completableFuture = new CompletableFuture<>();
    this.executorService.schedule(
            (() -> {
                try {
                    return completableFuture.complete(command.get());
                } catch (Throwable t) {
                    return completableFuture.completeExceptionally(t);
                }
            }),
            delay,
            unit
    );
    return completableFuture;
}

Here down a review version of your implementation:下面是您的实施的审查版本:

public class Sandbox2 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {


        MockApi mockApi = new MockApi();

        CompletableFuture<Integer> result1 = mockApi.requestAThing("Req1");
        CompletableFuture<Integer> result2 = mockApi.requestAThing("Req2");
        CompletableFuture<Integer> result3 = mockApi.requestAThing("Req3");

        System.out.println("Result1: " + result1.get());
        System.out.println("Result2: " + result2.get());
        System.out.println("Result3: " + result3.get());

    }

    public static class MockApi {

        private final AtomicLong delay = new AtomicLong(0);

        private final ScheduledExecutorService executorService = Executors.newSingleThreadScheduledExecutor();

        public CompletableFuture<Integer> requestAThing(String req1) {
            return this.scheduleCompletableFuture(new ActualApi().requestHandler(req1), delay.incrementAndGet(), TimeUnit.SECONDS);
        }

        public <T> CompletableFuture<T> scheduleCompletableFuture(
                final CompletableFuture<T> command,
                final long delay,
                final TimeUnit unit) {
            final CompletableFuture<T> completableFuture = new CompletableFuture<>();
            this.executorService.schedule(
                    (() -> {
                        try {
                            return completableFuture.complete(command.get());
                        } catch (Throwable t) {
                            return completableFuture.completeExceptionally(t);
                        }
                    }),
                    delay,
                    unit
            );
            return completableFuture;
        }
    }

    public static class ActualApi {

        public CompletableFuture<Integer> requestHandler(String request) {
            return CompletableFuture.supplyAsync(() -> {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                return Integer.parseInt(request.substring(3));
            });
        }
    }

}

JDK 9 and onward JDK 9 及更高版本

If you are using a JDK 9 version, you may make use of the supported delayed Executor :如果您使用的是JDK 9版本,则可以使用支持的延迟Executor

CompletableFuture<String> future = new CompletableFuture<>();
future.completeAsync(() -> {
    try {
        // do something
     } catch(Throwable e) {
        // do something on error
     }
  }, CompletableFuture.delayedExecutor(1, TimeUnit.SECONDS));

Your MockApi#requestAThing would then be cleaner and shorter and you are no more in need of a custom ScheduledExecutor :您的MockApi#requestAThing将变得更MockApi#requestAThing 、更短,并且您不再需要自定义ScheduledExecutor

public static class MockApi {

    private final AtomicLong delay = new AtomicLong(0);

    public CompletableFuture<Integer> requestAThing(String req1) {
        CompletableFuture<Void> future = new CompletableFuture<>();
        return future.completeAsync(() -> null, CompletableFuture.delayedExecutor(delay.incrementAndGet(), TimeUnit.SECONDS))
                .thenCombineAsync(new ActualApi().requestHandler(req1), (nil, result) -> result);
    }
// ...
}

您可能会考虑使用bucket4j

I have found a way to produce my desired behaviour.我找到了一种方法来产生我想要的行为。 By limiting my Executor to a single Thread I can queue up calls and they will follow the order I queued them up in.通过将我的 Executor 限制为单个线程,我可以将调用排队,它们将按照我将它们排队的顺序进行。

I will supply the code of my mock classes below for anyone interested:我将在下面为任何感兴趣的人提供我的模拟课程的代码:

import java.util.Random;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class Sandbox2 {

    public static void main(String[] args) throws ExecutionException, InterruptedException {


        MockApi mockApi = new MockApi();


        CompletableFuture<Integer> result1 = mockApi.requestAThing("Req1");
        System.out.println("Request1 queued up");
        CompletableFuture<Integer> result2 = mockApi.requestAThing("Req2");
        System.out.println("Request2 queued up");
        CompletableFuture<Integer> result3 = mockApi.requestAThing("Req3");
        System.out.println("Request3 queued up");

        //Some other logic happens here
        Thread.sleep(10000);

        System.out.println("Result1: " + result1.get());
        System.out.println("Result2: " + result2.get());
        System.out.println("Result3: " + result3.get());

        System.exit(0);
    }

    public static class MockApi {
        ActualApi actualApi = new ActualApi();
        private ExecutorService executorService = Executors.newSingleThreadExecutor();
        ;


        public CompletableFuture<Integer> requestAThing(String req1) {

            CompletableFuture<Integer> completableFutureCompletableFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    System.out.println("Waiting with " + req1);
                    waitForTheRightMoment(new Random().nextInt(1000) + 1000);
                    System.out.println("Done Waiting with " + req1);
                    return actualApi.requestHandler(req1).get();

                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                return null;
            }, executorService);


            return completableFutureCompletableFuture;
        }

        private void waitForTheRightMoment(int time) {
            try {
                Thread.sleep(time);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public static class ActualApi {

        public CompletableFuture<Integer> requestHandler(String request) {
            return CompletableFuture.supplyAsync(() -> {
                try {
                    Thread.sleep(new Random().nextInt(1000) + 1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("Request Handled " + request);
                return Integer.parseInt(request.substring(3));
            });
        }
    }
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

相关问题 如果满足特定条件,如何更改 Java 中字符串中的元音? - How do I change vowels in a string in Java if a certain condition is met? 满足特定条件时添加消息(Java) - Adding message when certain condition is met (Java) 满足条件后,如何让我的脚本运行另一个脚本? (Java) - How can I have my script run another script when a condition has been met? (Java) 有没有办法在Java中满足某个条件时结束递归方法? - Is there a way to end a recursive method when a certain condition is met in Java? 如何创建可以运行到满足特定条件的JUnit测试? - How can I create a JUnit test that runs until a certain condition is met? 满足特定条件时如何更改 android 中的启动器活动? - How to change launcher activity in android when a certain condition is met? 满足特定条件时停止递归方法 - Stop a recursive method when a certain condition is met 满足特定条件时画一个圆圈 - draw a circle when certain condition is met Android Java Runnable,当满足Runnable中的条件时,如何打开另一个活动? - Android Java Runnable, How do I open another activity when a condition in the runnable is met? 在满足条件之前,如何在Java程序中创建连续的For循环? - How can I create a continous For Loop within a Java program until a condition is met?
 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM