Terminate long running task inside background thread

Viewed 1027

I have a task that compress an image, which use many many loops inside it:

private void writeCompressedData() {
    int i, j, r, c, a, b;
    loat[][] inputArray;

    for (r = 0; r < minBlockHeight; r++) {
        for (c = 0; c < minBlockWidth; c++) {
            xpos = c * 8;
            pos = r * 8;
            for (comp = 0; comp < jpegObj.numberOfComponents; comp++) {
                inputArray = (float[][]) jpegObj.components[comp];

                 for (i = 0; i < jpegObj.VsampFactor[comp]; i++) {
                     for (j = 0; j < jpegObj.HsampFactor[comp]; j++) {
                         xblockoffset = j * 8;
                         yblockoffset = i * 8;
                         for (a = 0; a < 8; a++) {
                             for (b = 0; b < 8; b++) {
                                 // Process some data and put to inputArray
                             }
                         }

                         // Encode Huffman block 
                     }
                 }
             }
         }
     }
}

I run this method inside a normal thread like this:

new Thread(new Runnable() {
    @Override
    public void run() {
        writeCompressedData();
    }
});

Or run inside a background worker thread

TaskExecutor.queueRunnable(new Runnable() {
    @Override
    public void run() {
        writeCompressedData();
    }
});

The problem is: this method sometimes go wrong and cause infinite loops when receive invalid input. In that case, it will run forever and hurt the CPU even when the device's screen turn off which increase device's temperature (and if I use worker thread it also blocks other tasks inside waiting queue).

I think I need to set a timeout to terminate long running task. What's the best way to achieve this in normal Java thread? Does RxJava support it?


I know what I need to fix is the "wrong" method, not just terminate it. But for the big apps, it's hard to control other developer's code, and the first thing I need is avoid affecting users.

4 Answers

You'd need some form of cooperative cancellation, say checking Thread.currentThread().isInterrupted() inside one or more of the nested loops.

for (/* ... */) {
    if (Thread.currentThread().isInterrupted()) return;

    for (/* ... */) {

        if (Thread.currentThread().isInterrupted()) return;

        for (/* ... */) {
            // the tightest loop
        }             
    }
}

Then when you run the method, keep the Thread/Future and call interrupt/cancel(true):

backgroundTask = new Thread(() -> method());
backgroundTask.start();
// ...
backgroundTask.interrupt();

backgroundFuture = executorService.submit(() -> method());
// ...
backgroundFuture.cancel(true);

In RxJava this would look something like this:

backgroundDisposable = Completable.fromAction(() -> method())
.subscribeOn(Schedulers.io()) // dedicated thread recommended
.observeOn(AndroidSchedulers.mainThread())
.subscribe(() -> { /* done */ }, e -> { /* error */ });

// ...
backgroundDisposable.dispose();

You could use Java ExecutorService with timeout and Future to fix this problem. See this post.

A better way is to run such code in different process. Even the process crashed the main process will be un-impacted.

My Approach is with using RxJava and more specifically using Timeout operator of RxJava, Here is the gist of the code, After 5 seconds the onError will be called as there is no emission of an item within the previous 5 seconds,

private void timeOutObserver() {

        Observable.create(new ObservableOnSubscribe<String>() {
            @Override
            public void subscribe(ObservableEmitter<String> emitter) {
                emitter.onNext("A");
            }
        })
                .timeout(5000, TimeUnit.MILLISECONDS)
                .subscribeOn(Schedulers.io())
                .observeOn(AndroidSchedulers.mainThread())
                .subscribe(new Observer<String>() {
                    @Override
                    public void onSubscribe(Disposable d) {
                        Log.d("-@-", "subscribed");
                    }

                    @Override
                    public void onNext(String s) {
                        Log.d("-@-", "on next " + s);
                    }

                    @Override
                    public void onError(Throwable e) {
                        Log.d("-@-", e.getLocalizedMessage());
                    }

                    @Override
                    public void onComplete() {
                        Log.d("-@-", "on complete");
                    }
                });
    }

For more working on TimeOut operator refer this,

Related