Join multiple callback executions in a CompletableFuture - java

I have the following working code:
DiscoveryCallback callback = new DiscoveryCallback();
Manager.discover(someparam, callback);
I want to wrap this call into a CompletableFuture to have a Rx-ish API to compose with other async operations.
Manager.discover() is a method of a third-party library that is actually a binding for native functions and it executes the callback multiple times, in different threads.
My DiscoveryCallback implements the following interface:
interface onFoundListerner {
onFound(List<Result> results)
onError(Throwable error)
}
I tried to inject an instance of CompletableFuture<List<Result>> into DiscoveryCallback and then call the complete method. It works fine for one callback execution, the others are ignored.
How can I join the results of this multiple executions and make my wrapper return a single CompletableFuture ?

What about an asynchronous queue?
public class AsyncQueue<T> {
private final Object lock = new Object();
private final Queue<T> queue = new ArrayDeque<T>();
private CompletableFuture<Void> removeCf = new CompletableFuture<>();
public void add(T item) {
synchronized (lock) {
queue.add(item);
removeCf.complete(null);
}
}
public CompletableFuture<T> removeAsync() {
CompletableFuture<Void> currentCf = null;
synchronized (lock) {
T item = queue.poll();
if (item != null) {
return CompletableFuture.completedFuture(item);
}
else {
if (removeCf.isDone()) {
removeCf = new CompletableFuture<>();
}
currentCf = removeCf;
}
}
return currentCf
.thenCompose(v -> removeAsync());
}
}
In Java 9, you can use .completeOnTimeout(null, timeout, unit) on the CompletableFuture returned by removeAsync to have a timeout mechanism.
Before Java 9, you need to schedule your own timeouts. Here's a version with an embedded timeout scheduler:
public class AsyncQueue<T> {
static final ScheduledExecutorService scheduledExecutorService;
static {
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(1, new ScheduledThreadFactory());
scheduledThreadPoolExecutor.setRemoveOnCancelPolicy(true);
scheduledExecutorService = Executors.unconfigurableScheduledExecutorService(scheduledThreadPoolExecutor);
}
static final class ScheduledThreadFactory implements ThreadFactory {
static AtomicInteger scheduledExecutorThreadId = new AtomicInteger(0);
static final synchronized int nextScheduledExecutorThreadId() {
return scheduledExecutorThreadId.incrementAndGet();
}
#Override
public Thread newThread(Runnable runnable) {
Thread thread = new Thread(runnable, "AsynchronousSemaphoreScheduler-" + nextScheduledExecutorThreadId());
thread.setDaemon(true);
return thread;
}
}
private final Object lock = new Object();
private final Queue<T> queue = new ArrayDeque<T>();
private CompletableFuture<Long> removeCf = new CompletableFuture<>();
public void add(T item) {
synchronized (lock) {
queue.add(item);
removeCf.complete(System.nanoTime());
}
}
public CompletableFuture<T> removeAsync(long timeout, TimeUnit unit) {
if (unit == null) throw new NullPointerException("unit");
CompletableFuture<Long> currentCf = null;
synchronized (lock) {
T item = queue.poll();
if (item != null) {
return CompletableFuture.completedFuture(item);
}
else if (timeout <= 0L) {
return CompletableFuture.completedFuture(null);
}
else {
if (removeCf.isDone()) {
removeCf = new CompletableFuture<>();
}
currentCf = removeCf;
}
}
long startTime = System.nanoTime();
long nanosTimeout = unit.toNanos(timeout);
CompletableFuture<T> itemCf = currentCf
.thenCompose(endTime -> {
long leftNanosTimeout = nanosTimeout - (endTime - startTime);
return removeAsync(leftNanosTimeout, TimeUnit.NANOSECONDS);
});
ScheduledFuture<?> scheduledFuture = scheduledExecutorService
.schedule(() -> itemCf.complete(null), timeout, unit);
itemCf
.thenRun(() -> scheduledFuture.cancel(true));
return itemCf;
}
public CompletableFuture<T> removeAsync() {
CompletableFuture<Long> currentCf = null;
synchronized (lock) {
T item = queue.poll();
if (item != null) {
return CompletableFuture.completedFuture(item);
}
else {
if (removeCf.isDone()) {
removeCf = new CompletableFuture<>();
}
currentCf = removeCf;
}
}
return currentCf
.thenCompose(endTime -> removeAsync());
}
}
You can refactor the scheduler out of this class to share it with other classes, perhaps into a singleton which uses a factory set up in a .properties file and which resorts to the default in the example if not configured.
You can use a ReentrantLock instead of the synchronized statement to gain that little bit of performance. It should only matter under heavy contention, but AsyncQueue<T> could be used for such purposes.

Related

How to add batching implicit for client?

Lets consider following code:
Client code:
public class MyClient {
private final MyClientSideService myClientSideService;
public MyClient(MyClientSideService myClientSideService) {
this.myClientSideService = myClientSideService;
}
public String requestRow(Integer req) {
return myClientSideService.requestSingleRow(req);
}
}
Client side service:
public class MyClientSideService {
private final MyServerSideService myServerSideService;
public MyClientSideService(MyServerSideService myServerSideService) {
this.myServerSideService = myServerSideService;
}
public String requestSingleRow(int req) {
return myServerSideService.requestRowBatch(Arrays.asList(req)).get(0);
}
}
Server side service:
#Slf4j
public class MyServerSideService {
//single threaded bottleneck service
public synchronized List<String> requestRowBatch(List<Integer> batchReq) {
log.info("Req for {} started");
try {
Thread.sleep(100);
return batchReq.stream().map(String::valueOf).collect(Collectors.toList());
} catch (InterruptedException e) {
return null;
} finally {
log.info("Req for {} finished");
}
}
}
And main:
#Slf4j
public class MainClass {
public static void main(String[] args) {
MyClient myClient = new MyClient(new MyClientSideService(new MyServerSideService()));
for (int i = 0; i < 20; i++) {
new Thread(() -> {
for (int m = 0; m < 100; m++) {
int k = m;
log.info("Response is {}", myClient.requestRow(k));
}
}).start();
}
}
}
According the logs it takes approximately 4 min 22 sec but it too much. Ithink it might be improved dramatically. I would like to implement implicit batching. So MyClientSideService should collect requests and when it becomes 50(it is preconfigured batch size) or some preconfigured timeout expired then to request MyServerSideService and back route result to the clients. Protocol should be synchronous so clients must be blocked until result getting.
I tried to write code using CountDownLatches and CyclicBarriers but my attempts were far from success.
How can I achieve my goal?
P.S.
If to replace requestRowBatch return type List<String> from to Map<Integer, String> to delegate request and response mapping to server following works with limititations. It works only if I send <=25 requests
#Slf4j
public class MyClientSideService {
private final Integer batchSize = 25;
private final Integer maxTimeoutMillis = 5000;
private final MyServerSideService myServerSideService;
private final Queue<Integer> queue = new ArrayBlockingQueue(batchSize);
private final Map<Integer, String> responseMap = new ConcurrentHashMap();
private final AtomicBoolean started = new AtomicBoolean();
private CountDownLatch startBatchRequestLatch = new CountDownLatch(batchSize);
private CountDownLatch awaitBatchResponseLatch = new CountDownLatch(1);
public MyClientSideService(MyServerSideService myServerSideService) {
this.myServerSideService = myServerSideService;
}
public String requestSingleRow(int req) {
queue.offer(req);
if (!started.compareAndExchange(false, true)) {
log.info("Start batch collecting");
startBatchCollecting();
}
startBatchRequestLatch.countDown();
try {
log.info("Awaiting batch response latch for {}...", req);
awaitBatchResponseLatch.await();
log.info("Finished awaiting batch response latch for {}...", req);
return responseMap.get(req);
} catch (InterruptedException e) {
e.printStackTrace();
return "EXCEPTION";
}
}
private void startBatchCollecting() {
new Thread(() -> {
try {
log.info("Await startBatchRequestLatch");
startBatchRequestLatch.await(maxTimeoutMillis, TimeUnit.MILLISECONDS);
log.info("await of startBatchRequestLatch finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
responseMap.putAll(requestBatch(queue));
log.info("Releasing batch response latch");
awaitBatchResponseLatch.countDown();
}).start();
}
public Map<Integer, String> requestBatch(Collection<Integer> requestList) {
return myServerSideService.requestRowBatch(requestList);
}
}
Update
According Malt answer I was able to develop following:
#Slf4j
public class MyClientSideServiceCompletableFuture {
private final Integer batchSize = 25;
private final Integer maxTimeoutMillis = 5000;
private final MyServerSideService myServerSideService;
private final Queue<Pair<Integer, CompletableFuture>> queue = new ArrayBlockingQueue(batchSize);
private final AtomicInteger counter = new AtomicInteger(0);
private final Lock lock = new ReentrantLock();
public MyClientSideServiceCompletableFuture(MyServerSideService myServerSideService) {
this.myServerSideService = myServerSideService;
}
public String requestSingleRow(int req) {
CompletableFuture<String> future = new CompletableFuture<>();
lock.lock();
try {
queue.offer(Pair.of(req, future));
int counter = this.counter.incrementAndGet();
if (counter != 0 && counter % batchSize == 0) {
log.info("request");
List<Integer> requests = queue.stream().map(p -> p.getKey()).collect(Collectors.toList());
Map<Integer, String> serverResponseMap = requestBatch(requests);
queue.forEach(pair -> {
String response = serverResponseMap.get(pair.getKey());
CompletableFuture<String> value = pair.getValue();
value.complete(response);
});
queue.clear();
}
} finally {
lock.unlock();
}
try {
return future.get();
} catch (Exception e) {
return "Exception";
}
}
public Map<Integer, String> requestBatch(Collection<Integer> requestList) {
return myServerSideService.requestRowBatch(requestList);
}
}
But it doesn't work if size is not multiple of batch size
If to replace requestRowBatch return type from List<String> with Map<Integer, String> to delegate request and response mapping to server I was able to crete following solution:
#Slf4j
public class MyClientSideServiceCompletableFuture {
private final Integer batchSize = 25;
private final Integer timeoutMillis = 5000;
private final MyServerSideService myServerSideService;
private final BlockingQueue<Pair<Integer, CompletableFuture>> queue = new LinkedBlockingQueue<>();
private final Lock lock = new ReentrantLock();
private final Condition requestAddedCondition = lock.newCondition();
public MyClientSideServiceCompletableFuture(MyServerSideService myServerSideService) {
this.myServerSideService = myServerSideService;
startQueueDrainer();
}
public String requestSingleRow(int req) {
CompletableFuture<String> future = new CompletableFuture<>();
while (!queue.offer(Pair.of(req, future))) {
log.error("Can't add {} to the queue. Retrying...", req);
}
lock.lock();
try {
requestAddedCondition.signal();
} finally {
lock.unlock();
}
try {
return future.get();
} catch (Exception e) {
return "Exception";
}
}
private void startQueueDrainer() {
new Thread(() -> {
log.info("request");
while (true) {
ArrayList<Pair<Integer, CompletableFuture>> requests = new ArrayList<>();
if (queue.drainTo(requests, batchSize) > 0) {
log.info("drained {} items", requests.size());
Map<Integer, String> serverResponseMap = requestBatch(requests.stream().map(Pair::getKey).collect(Collectors.toList()));
requests.forEach(pair -> {
String response = serverResponseMap.get(pair.getKey());
CompletableFuture<String> value = pair.getValue();
value.complete(response);
});
} else {
lock.lock();
try {
while (queue.size() == 0) {
try {
log.info("Waiting on condition");
requestAddedCondition.await(timeoutMillis, TimeUnit.MILLISECONDS);
log.info("Waking up on condition");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
} finally {
lock.unlock();
}
}
}
}).start();
}
public Map<Integer, String> requestBatch(Collection<Integer> requestList) {
return myServerSideService.requestRowBatch(requestList);
}
}
It looks like a working solution. But I am not sure if it is optimal.
Your MyClientSideServiceCompletableFuture solution, will send the requests to the server every time you add something to the queue and doesnt wait for requests to be batch sized. You are using BlockingQueue and adding the uneccessary blocking condition and locks. BlockingQueue has blocking-timeout capabilites so no addition condition is neccessary.
You can simplify your solution like this:
It sends requests to server only when the batch is full or the timeout passed and batch is not empty.
private void startQueueDrainer() {
new Thread(() -> {
log.info("request");
ArrayList<Pair<Integer, CompletableFuture>> batch = new ArrayList<>(batchSize);
while (true) {
try {
batch.clear(); //clear batch
long timeTowWait = timeoutMillis;
long startTime = System.currentTimeMillis();
while (timeTowWait > 0 && batch.size() < batchSize) {
Pair<Integer, CompletableFuture> request = queue.poll(timeTowWait , TimeUnit.MILLISECONDS);
if(request != null){
batch.add(request);
}
long timeSpent = (System.currentTimeMillis() - startTime);
timeTowWait = timeTowWait - timeSpent;
}
if (!batch.isEmpty()) {
// we wait at least timeoutMillis or batch is full
log.info("send {} requests to server", batch.size());
Map<Integer, String> serverResponseMap = requestBatch(batch.stream().map(Pair::getKey).collect(Collectors.toList()));
batch.forEach(pair -> {
String response = serverResponseMap.get(pair.getKey());
CompletableFuture<String> value = pair.getValue();
value.complete(response);
});
} else {
log.info("We wait {} but the batch is still empty", System.currentTimeMillis() - startTime);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
Change the method requestSingleRow to not use lock
public String requestSingleRow(int req) {
CompletableFuture<String> future = new CompletableFuture<>();
while (!queue.offer(Pair.of(req, future))) {
log.error("Can't add {} to the queue. Retrying...", req);
}
try {
return future.get();
} catch (Exception e) {
return "Exception";
}
}
You could use CompletableFuture.
Have threads calling MyClientSideService put their request in a Queue (possibly BlockingQueue, and get a new CompletableFuture in return. The calling thread can call CompletableFuture.get() to block until a result is ready, or go on doing other things.
That CompletableFuture will be stored together with the request in MyClientSideService. When you reach 50 requests (and therefore 50 CompletableFuture instances), have the client service send the batch request.
When the request is complete, use the CompletableFuture.complete(value) method of each ComplatableFuture instance in the queue to notify the client thread that the response is ready. This will unblock the client if it has called blocking method like CompletableFuture.get(), or make it return instantly with value if called later.

How do I get results from my work queue task?

I've implemented a simply work queue that receives tasks from a number of different threads. I want these tasks to return a value to their source thread, but can't figure out how to do that.
I've considered using a future, but there's no way to explicitly set the future's value. I could use a property, but I don't believe those are thread safe.
Every task is an implementation of DBRequest. The actual content varies, but the result of all activities is a string.
An asynchronous thread creates a DBRequest and submits it to the queue. The queue runs the task, which produces a string. How do I get that string back to the thread that created the DBRequest, and how can I cause my creator thread to wait for the result?
public interface DBRequest {
String execute(VdtsSysDB vdtsSysDB, BoardLoad currentLoad);
}
public class DBQueue implements Runnable {
private static DBQueue dbQueue;
private LinkedBlockingQueue<DBRequest> queue = new LinkedBlockingQueue<>();
private VdtsSysDB vdtsSysDB = new VdtsSysDB();
private ReentrantLock lock = new ReentrantLock();
private static final Logger LOG = LoggerFactory.getLogger(DBQueue.class);
private boolean kill = false;
private BoardLoad currentLoad;
private ProgressController progressController;
public static DBQueue getInstance() {
if (dbQueue == null) synchronized (DBQueue.class) {
if (dbQueue == null)
dbQueue = new DBQueue();
}
return dbQueue;
}
private DBQueue() {
}
public ReentrantLock getLock() {
return lock;
}
#Override
public void run() {
LOG.info("Starting DBQueue loop. Kill {}.", kill);
while (!kill) {
DBRequest dbRequest = removeRequest();
if (dbRequest != null) {
lock.lock();
String result = dbRequest.execute(vdtsSysDB, currentLoad);
lock.unlock();
if (progressController != null) Platform.runLater(() ->
progressController.updateDisplay(currentLoad));
}
}
vdtsSysDB.getEntityManager().close();
}
public void addRequest(DBRequest dbRequest) {
try {
queue.add(dbRequest);
LOG.info("Added request.");
} catch (Exception e) {
LOG.error("Can't add element.", e);
}
}
private DBRequest removeRequest() {
DBRequest result = null;
try {
//result = queue.poll(10, TimeUnit.SECONDS);
result = queue.take();
} catch (Exception e) {
LOG.error("Exception.", e);
}
return result;
}
public void killDBQueue() {
kill = true;
LOG.info("Shutting down DBQueue.");
}
public static void start() {
Thread thread = new Thread(DBQueue.getInstance(), "DBQueue Thread");
thread.start();
LOG.info("Starting DBQueue.");
}
public BoardLoad getCurrentLoad() {
if (currentLoad == null)
currentLoad = BoardLoad.getLastOpenLoad(vdtsSysDB);
return currentLoad;
}
public void setCurrentLoad(BoardLoad proposedLoad) {
// We can only have one open load, and by definition, the current load is open. So close it.
if (this.currentLoad != null && !this.currentLoad.equals(proposedLoad)) {
currentLoad.close(vdtsSysDB);
if (proposedLoad != null) {
this.currentLoad = vdtsSysDB.getEntityManager().find(BoardLoad.class, proposedLoad.getId());
} else this.currentLoad = null;
}
}
public ProgressController getProgressController() {
return progressController;
}
public void setProgressController(ProgressController progressController) {
this.progressController = progressController;
}
}
EDIT: I'm using this queue to synchronize database access, reducing the need for locks and ensuring that requests are completed sequentially. I don't believe there is any other way to achieve this sort of asynchronous request -> synchronous request change.
But I'd love to have that belief changed.
You should add a reference to the submitting thread in your DBRequest interface and implement a setResult(String result) (or similar) method to receive the result.
You can implement a CountDownLatch waiting (or similar) on your submitting thread run() method to wait setting latch up when sending request to queue and down in setResult method.
If I'm not clear just let me know and I'll elaborate.

Callable How to prevent call() from returning value

Is there a way to prevent call() from returning value until for example a Boolean is set? So that i can control when futureCall.get() is done?
Main-class:
ExecutorService executor = Executors.newCachedThreadPool();
Future<List<Float>> futureCall = executor.submit((Callable<List<Float>>) new AxisMeasuring(2,100,this));
List<Float> jumpValues;
try {
jumpValues = futureCall.get();
} catch (InterruptedException | ExecutionException e) {
e.printStackTrace();
}
Callable-class:
public class AxisMeasuring implements SensorEventListener, Callable<List<Float>>{
AxisMeasuring(int _axis, final int _timeDelay, Context _context) {
axis = _axis;
final Context context = _context;
timeDelay = _timeDelay;
handler = new Handler();
runnable = new Runnable() {
#Override
public void run() {
values.add(value);
if (!hadZeroValue && value <= 1) {
hadZeroValue = true;
}
if (hadZeroValue && value >= 12) {
Log.d("Debug","Point reached");
} else {
handler.postDelayed(runnable, timeDelay);
}
}
};
handler.post(runnable);
}
#Override
public List<Float> call() throws Exception {
return values;
}
}
futureCall.get() returns null instantly.
Yes, use a CountDownLatch with count 1.
CountDownLatch latch = new CountDownLatch(1);
and pass this latch to AxisMeasuring:
public class AxisMeasuring implements SensorEventListener, Callable<List<Float>>{
private CountDownLatch latch;
AxisMeasuring(int _axis, final int _timeDelay, Context _context, CountDownLatch latch) {
latch = latch;
...
}
#Override
public List<Float> call() throws Exception {
latch.await(); // this will get blocked until you call latch.countDown after, for example, a Boolean is set
return values;
}
}
in other thread, you can call latch.countDown() as signal.

Android how to group async tasks together like in iOS

I have a function in iOS app that uses dispatch_group to group multiple rest request:
static func fetchCommentsAndTheirReplies(articleId: String, failure: ((NSError)->Void)?, success: (comments: [[String: AnyObject]], replies: [[[String: AnyObject]]], userIds: Set<String>)->Void) {
var retComments = [[String: AnyObject]]()
var retReplies = [[[String: AnyObject]]]()
var retUserIds = Set<String>()
let queue = dispatch_get_global_queue(QOS_CLASS_USER_INITIATED, 0)
Alamofire.request(.GET, API.baseUrl + API.article.listCreateComment, parameters: [API.article.articleId: articleId]).responseJSON {
response in
dispatch_async(queue) {
guard let comments = response.result.value as? [[String: AnyObject]] else {
failure?(Helper.error())
return
}
print(comments)
retComments = comments
let group = dispatch_group_create()
for (commentIndex, comment) in comments.enumerate() {
guard let id = comment["_id"] as? String else {continue}
let relevantUserIds = helperParseRelaventUserIdsFromEntity(comment)
for userId in relevantUserIds {
retUserIds.insert(userId)
}
retReplies.append([[String: AnyObject]]())
dispatch_group_enter(group)
Alamofire.request(.GET, API.baseUrl + API.article.listCreateReply, parameters: [API.article.commentId: id]).responseJSON {
response in
dispatch_async(queue) {
if let replies = response.result.value as? [[String: AnyObject]] {
for (_, reply) in replies.enumerate() {
let relevantUserIds = helperParseRelaventUserIdsFromEntity(reply)
for userId in relevantUserIds {
retUserIds.insert(userId)
}
}
retReplies[commentIndex] = replies
}
dispatch_group_leave(group)
}
}
}
dispatch_group_wait(group, DISPATCH_TIME_FOREVER)
success(comments: retComments, replies: retReplies, userIds: retUserIds)
}
}
}
As you can see from my code, I fetch all the comments under the same article, then fetch coresponding replies under each comment. After all requests are done, I invoke my success callback. This can be achieved using GCD's dispatch_group.
Now I am migrating the same functionality to android.
public static void fetchCommentsAndTheirReplies(Context context, String articleId, final StringBuffer outErrorMessage, final Runnable failure, final ArrayList<JSONObject> outComments, final ArrayList<ArrayList<JSONObject>> outReplies, final HashSet<String> outUserIds, final Runnable success) {
final RequestQueue queue = Volley.newRequestQueue(context);
HashMap<String, String> commentParams = new HashMap<>();
commentParams.put(API.article.articleId, articleId);
JsonArrayRequest commentRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateComment, new JSONObject(commentParams), new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
try {
for (int i = 0; i < response.length(); i++) {
JSONObject comment = response.getJSONObject(i);
outComments.add(comment);
outUserIds.addAll(helperParseRelaventUserIdsFromEntity(comment));
outReplies.add(new ArrayList<JSONObject>());
//TODO: DISPATCH_GROUP?
String id = comment.getString("_id");
HashMap<String, String> replyParams = new HashMap<>();
replyParams.put(API.article.commentId, id);
final int finalI = i;
JsonArrayRequest replyRequest = new JsonArrayRequest(Request.Method.GET, API.baseUrl + API.article.listCreateReply, new JSONObject(replyParams), new Response.Listener<JSONArray>() {
#Override
public void onResponse(JSONArray response) {
try {
for (int j = 0; j < response.length(); j++) {
JSONObject reply = response.getJSONObject(j);
outUserIds.addAll(helperParseRelaventUserIdsFromEntity(reply));
outReplies.get(finalI).add(reply);
}
} catch (JSONException ex) {}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {}
});
queue.add(replyRequest);
}
success.run();
} catch (JSONException ex) {}
}
}, new Response.ErrorListener() {
#Override
public void onErrorResponse(VolleyError error) {
outErrorMessage.append(error.getMessage());
failure.run();
}
});
queue.add(commentRequest);
}
Note that I am using success is executed right after I get all the comments, and before getting all the replies.
So how can I group them and delay the response?
I am working on the hairy implementation like
taskCount++;
if (taskCount == totalCount) {
success.run();
}
in reply block, but it seems very tedious.
You can simply do it with this class I made to mimic the iOS behavior. Call enter() and leave() the same way you did in iOS with dispatch_group_enter and dispatch_group_leave and call notify() just after the requests you want to group, just like dispatch_group_notify. It also uses runnable the same way iOS uses blocks :
public class DispatchGroup {
private int count = 0;
private Runnable runnable;
public DispatchGroup()
{
super();
count = 0;
}
public synchronized void enter(){
count++;
}
public synchronized void leave(){
count--;
notifyGroup();
}
public void notify(Runnable r) {
runnable = r;
notifyGroup();
}
private void notifyGroup(){
if (count <=0 && runnable!=null) {
runnable.run();
}
}
}
Hope it helps ;)
Here is the Kotlin version of Damien Praca's answer. This will allow you to use Kotlin lambdas like this.
val dispatchGroup = DispatchGroup()
dispatchGroup.enter()
// Some long running task
dispatchGroup.leave()
dispatchGroup.notify {
// Some code to run after all dispatch groups complete
}
class DispatchGroup {
private var count = 0
private var runnable: (() -> Unit)? = null
init {
count = 0
}
#Synchronized
fun enter() {
count++
}
#Synchronized
fun leave() {
count--
notifyGroup()
}
fun notify(r: () -> Unit) {
runnable = r
notifyGroup()
}
private fun notifyGroup() {
if (count <= 0 && runnable != null) {
runnable!!()
}
}
}
There is no direct analogue of dispatch_group in plain Java or Android. I can recommend a few rather sophisticated techniques to produce a really clean and elegant solution if you're ready to invest some extra time in it. It's not gonna be one or two lines of code, unfortunately.
Use RxJava with parallelization. RxJava provides a clean way to dispatch multiple tasks, but it works sequentially by default. See this article to make it execute tasks concurrently.
Although this is not exactly the intended usecase, you can try the ForkJoinPool to execute your group of tasks and recieve a single result afterwards.
You may use Threads and Thread.join() with Handlers as an option.
quote from:https://docs.oracle.com/javase/tutorial/essential/concurrency/join.html
The join method allows one thread to wait for the completion of
another. If t is a Thread object whose thread is currently executing,
t.join(); causes the current thread to pause execution until t's
thread terminates. Overloads of join allow the programmer to specify a
waiting period. However, as with sleep, join is dependent on the OS
for timing, so you should not assume that join will wait exactly as
long as you specify.
Like sleep, join responds to an interrupt by exiting with an
InterruptedException.
EDIT:
You should also check my event dispatcher gist. You may like it.
I use java.util.concurrent.CountDownLatch to achieve the goal.
First of all I made a interface for each task.
interface GroupTask {
void onProcessing(final CountDownLatch latch);
}
Then I create a class to handle grouping tasks.
interface MyDisptchGroupObserver {
void onAllGroupTaskFinish();
}
class MyDisptchGroup {
private static final int MSG_ALLTASKCOMPLETED = 300;
private CountDownLatch latch;
private MyDisptchGroupObserver observer;
private MsgHandler msgHandler;
private class MsgHandler extends Handler {
MsgHandler(Looper looper) {
super(looper);
}
#Override
public void handleMessage(Message msg) {
switch(msg.what) {
case MSG_ALLTASKCOMPLETED:
observer.onAllGroupTaskFinish();
break;
default:
break;
}
}
}
MyDisptchGroup(List<GroupTask> tasks, MyDisptchGroupObserver obj) {
latch = new CountDownLatch(tasks.size());
observer = obj;
msgHandler = new MsgHandler(getActivity().getMainLooper())
new Thread( new Runnable() {
#Override
public void run() {
try {
latch.await();
Log.d(TAG, "========= All Tasks Completed =========");
msgHandler.sendEmptyMessage(MSG_ALLTASKCOMPLETED);
} catch() {
e.printStackTrace();
}
}
}).start();
for( GroupTask task : tasks ) {
task.onProcessing(latch);
}
}
}
Of course I have more than one task implementation as the following.
The Task1
class Task1 implements GroupTask {
#Override
public void onProcessing(final CountDownLatch latch) {
new Thread( new Runnable() {
#Override
public void run() {
// Just implement my task1 stuff here
// The end of the Task1 remember to countDown
latch.countDown();
}
}).start();
}
}
And Task2
class Task2 implements GroupTask {
#Override
public void onProcessing(final CountDownLatch latch) {
new Thread( new Runnable() {
#Override
public void run() {
// Just implement my task2 stuff here
// The end of the Task2 remember to countDown
latch.countDown();
}
}).start();
}
}
Now everything are ready to fire.
ArrayList<GroupTask> allTasks = new ArrayList<GroupTask>();
allTasks.add(new Task1());
allTasks.add(new Task2());
new MyDisptchGroup(allTasks, this);

Java asynchronous execution increases CPU 100%

Introduction:
I've developed a class which would accept number of Tasks, execute them in parallel and await for results particular amount of time. If some of the tasks failed to finish by given timeout it will interrupt entire execution and return only available results.
Issue:
All works fine at the beginning but after some time CPU usage increases until 100% and application obviously fails to response.
Could you please try to help me find an issue or suggest better solution how I could achieve the same goal?
Code:
TaskService.java
public abstract class TaskService {
private static final org.slf4j.Logger InfoLogger = LoggerFactory.getLogger("InfoLogger");
private static final org.slf4j.Logger ErrorLogger = LoggerFactory.getLogger("ErrorLogger");
#Autowired
private TimeLimiter timeLimiter;
public List<TaskResult> execute(TaskType taskType, TimeUnit timeUnit, long timeout, final Task... tasks){
final List<TaskResult> taskResultsStorage = new ArrayList<>();
try {
timeLimiter.callWithTimeout(new Callable<List<TaskResult>>() {
#Override
public List<TaskResult> call() throws Exception {
return run(taskResultsStorage, tasks);
}
}, timeout, timeUnit, true);
} catch (UncheckedTimeoutException e) {
String errorMsg = String.format("Time out of [%s] [%s] has been exceeded for task type:[%s]", timeout, timeUnit.name(), taskType.name());
ErrorLogger.error(errorMsg, e);
} catch (Exception e) {
String errorMsg = String.format("Unexpected error for task type:[%s]", taskType.name());
ErrorLogger.error(errorMsg, e);
}
return taskResultsStorage;
}
protected abstract List<TaskResult> run(List<TaskResult> taskResults,Task... tasks) throws ExecutionException, InterruptedException;
}
AsynchronousTaskService.java
public class AsynchronousTaskService extends TaskService {
private CompletionService<TaskResult> completionService;
public AsynchronousTaskService(ThreadExecutorFactory threadExecutorFactory){
this.completionService = new ExecutorCompletionService<TaskResult>(threadExecutorFactory.getExecutor());
}
#Override
protected List<TaskResult> run(List<TaskResult> resultStorage, Task... tasks) throws ExecutionException, InterruptedException {
List<Future<TaskResult>> futureResults = executeTask(tasks);
awaitForResults(futureResults, resultStorage);
return resultStorage;
}
private List<Future<TaskResult>> executeTask(Task... tasks){
List<Future<TaskResult>> futureTaskResults = new ArrayList<>();
if(tasks!=null) {
for (Task task : tasks) {
if (task != null) {
futureTaskResults.add(completionService.submit(task));
}
}
}
return futureTaskResults;
}
private void awaitForResults(List<Future<TaskResult>> futureResults, List<TaskResult> resultStorage) throws ExecutionException, InterruptedException {
int submittedTasks = futureResults.size();
int taskCompleted = 0;
if(futureResults != null){
while(taskCompleted < submittedTasks){
Iterator<Future<TaskResult>> it = futureResults.iterator();
while(it.hasNext()){
Future<TaskResult> processingTask = it.next();
if(processingTask.isDone()){
TaskResult taskResult = processingTask.get();
resultStorage.add(taskResult);
it.remove();
taskCompleted++;
}
}
}
}
}
}
ThreadExecutorFactory.java
#Component
public class ThreadExecutorFactory {
private int THREAD_LIMIT = 100;
private final Executor executor;
public ThreadExecutorFactory() {
executor = Executors.newFixedThreadPool(THREAD_LIMIT,
new ThreadFactory() {
public Thread newThread(Runnable r) {
Thread t = new Thread(r);
t.setDaemon(true);
return t;
}
});
}
public Executor getExecutor() {
return executor;
}
}
Task.java
public abstract class Task<T extends TaskResult> implements Callable<T> {
}
TaskResult.java
public abstract class TaskResult {
}
Your method awaitForResults contains a busy loop:
while(taskCompleted < submittedTasks){
...
while(it.hasNext()){
This will eat CPU like crazy, and hinders the actual threads. You should either add a sleep like for instance
Thread.sleep(1000);
This is Quick&Dirty but will help solving the 100% cpu. Alternatively but more effort is to implement some signalling mechanism so the loop waits for a signal from one of the finished tasks.
Like others suggested, it likely doesn't make sense to have 100 threads if they're all cpu-bound, but I doubt that is really your problem.

Categories

Resources