I have a long-running task of creating a bitmap saving it and recreating more bitmaps which I was doing on a single background thread
ExecutorService executor = Executors.newSingleThreadExecutor();
Handler handler = new Handler(Looper.getMainLooper());
executor.execute(() -> {...bitmap work ...}
But it's taking really long time to process all the bitmaps so I created a thread pool to use multithreading to speed up the task.
private final int cores = Runtime.getRuntime().availableProcessors();
private final ExecutorService executor = Executors.newFixedThreadPool(cores 1);
for (int i = 0; i < totalPage; i ) {
Runnable runnable = () -> {...bitmap work ...}
executor.submit(runnable);
}
But whenever I am using more than 1 thread it gets stuck randomly on some task(say 7 out of 127) there is no error or anything it just doesn't process any more tasks. I can view the pending task in the executor queue. But If I changed the thread pool to use 1 thread, it works without any problem and processes all tasks.
Here is the full actual code
ExecutorService executor = Executors.newFixedThreadPool(cores 1);
List<Future<?>> futureList = new ArrayList<>();
boolean allDone = false;
try {
//Convert pdf to Bitmap
ParcelFileDescriptor parcelFileDescriptor = ParcelFileDescriptor.open(new File(pdfFileName), ParcelFileDescriptor.MODE_READ_ONLY);
PdfRenderer pdfRenderer = new PdfRenderer(parcelFileDescriptor);
int totalPage = pdfRenderer.getPageCount();
final int[] counter = {1};
for (int i = 0; i < totalPage; i ) {
int finalI = i;
String finalOriginalPdfName = originalPdfName;
String finalGeneratedPdfName = generatedPdfName;
Runnable runnable = () -> {
//pd.setMessage("Processing page " (finalI 1) " of " totalPage);
PdfRenderer.Page page = pdfRenderer.openPage(finalI);
Bitmap pageBitmap = Bitmap.createBitmap((300 * page.getWidth()) / 72, (300 * page.getHeight()) / 72, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(pageBitmap);
canvas.drawColor(Color.WHITE);
canvas.drawBitmap(pageBitmap, 0, 0, null);
page.render(pageBitmap, null, null, PdfRenderer.Page.RENDER_MODE_FOR_PRINT);
page.close();
//Crop bitmaps and temporarily store on app data directory
for (int k = 0; k < SlipBoundingBox.Y.length; k ) {
for (int j = 0; j < SlipBoundingBox.X.length; j ) {
Bitmap slipBitmap = Bitmap.createBitmap(pageBitmap, SlipBoundingBox.X[j], SlipBoundingBox.Y[k], SlipBoundingBox.WIDTH, SlipBoundingBox.HEIGHT);
//Filename formation originalPdfName_generatePdfName_pdfPageIndex_x_y.extension
File slip = new File(
getExternalFilesDir("slips")
"/"
finalOriginalPdfName
"_"
finalGeneratedPdfName
"_"
finalI
"_"
SlipBoundingBox.X[j]
"_"
SlipBoundingBox.Y[k]
"_.jpg");
try (FileOutputStream out1 = new FileOutputStream(slip)) {
slipBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out1);
} catch (IOException e) {
e.printStackTrace();
}
slipBitmap.recycle();
}
}
pageBitmap.recycle();
pd.setMessage("Processed " counter[0] " of " totalPage " pages");
counter[0] ;
};
Future<?> future = executor.submit(runnable);
Log.d(TAG, "processPdf: " future.isDone());
futureList.add(future);
}
//Todo close pdfrender on all page processed
//pdfRenderer.close();
} catch (Exception e) {
e.printStackTrace();
}
CodePudding user response:
I am truly sorry I cannot directly help you. I do not know the inner workings of the PdfRenderer. I assume three things might pose the problem:
PdfRenderer has a deadlock somewhere inside its library. Unlikely.
Your file I/O is so complex, that writing multiple files in parallel simply takes 100 times longer. Remember the old Compact Disk (CD)? When reading one source you had full speed, as soon as 2 processes read, it became exponentially slow (not HALF as fast, but rather 20 times slower for 2 processes). I think this is the most likely reason.
There's an I/O error happening somewhere deep inside the PdfRenderer. Maybe it's opening too many file handles or whatever, leading to an unexpected crash that simply stalls execution right where it is.
So the only thing I can give you is this: analyse if any of the processes work (at least slowly). There are probably some frameworks for that, but I don't know them. There's also a lot of tools that can analyze JVMs at runtime. Here is a simpler solution to it:
Check in what state each of the Threads is, and if they change methods at all. This is purely visual right now:
- check out/use the code below. run it as-is
- you will see that each thread changes task/state from time to time
- if you like it, add my file to your project.
- adjust your code (see my main() method)
- reduce/limit the pool size to 3 or 4
- The console output will visually show you if there are any changes in any of the thread.
- if there are no changes at all, then the pdf lib is stuck
- if there are repeating changes, but nothing progresses longterm, the lib is caught in a loop of death
The Code:
package stackoverflow;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
public class SimpleThreadpoolAnalysis {
static private final long sStartMS = System.currentTimeMillis();
static public long now() {
return System.currentTimeMillis() - sStartMS;
}
static public void sleep(final long pMS) {
try {
Thread.sleep(pMS);
} catch (final InterruptedException e) { /* */ }
}
private final Set<Thread> mWorkingThreads = Collections.synchronizedSet(new HashSet<>());
private int mPrintStackDepth = 5;
public void setPrintStackDepth(final int pPrintStackDepth) {
mPrintStackDepth = pPrintStackDepth;
}
public void runChecked(final Runnable pLambda) {
if (pLambda == null) return;
final Thread currentThread = Thread.currentThread();
try {
System.out.println("SimpleThreadpoolAnalysis.runChecked() adding thread " currentThread.getName());
mWorkingThreads.add(currentThread);
pLambda.run();
} finally {
System.out.println("SimpleThreadpoolAnalysis.runChecked() removing thread " currentThread.getName());
mWorkingThreads.remove(currentThread);
}
}
public void printMiniStackTraces() {
System.out.println("Working Threads at " now());
for (final Thread t : mWorkingThreads) {
System.out.println("\tThread " t.getId() ": " t.getName());
final StackTraceElement[] st = t.getStackTrace();
for (int i = 0; i < Math.min(st.length, mPrintStackDepth); i ) {
System.out.println("\t\t" st[i]);
}
}
}
public Thread runSupervisorThread(final int pUpdateEveryXMs, final long pDurationMS) {
System.out.println("Supervisor Thread starting...");
final Thread t = new Thread(() -> runSupervisorThread_(pUpdateEveryXMs, pDurationMS), "Pool Supervisor Thread");
t.setDaemon(true);
t.start();
return t;
}
private void runSupervisorThread_(final int pUpdateEveryXMs, final long pDurationMS) {
System.out.println("Supervisor Thread starting...");
final int NUMBER_OF_RUNS = (int) (pDurationMS / pUpdateEveryXMs);
for (int i = 0; i < NUMBER_OF_RUNS; i ) {
System.out.flush();
sleep(pUpdateEveryXMs);
printMiniStackTraces();
}
System.out.println("Supervisor Thread ending...");
}
// this is my dummy class. you can remove this once you use it on your code
static public class TestWorker implements Runnable {
@Override public void run() {
while (true) {
// final int no =
subDelegator();
// System.out.println("Got " no);
}
}
private int subDelegator() {
SimpleThreadpoolAnalysis.sleep((long) (Math.random() * 1000));
final int randomIndex = (int) (Math.random() * 10);
switch (randomIndex) {
case 0:
return run_0();
case 1:
return run_1();
case 2:
return run_2();
case 3:
return run_3();
case 4:
return run_4();
case 5:
return run_5();
default:
return -1;
}
}
private int run_0() {
SimpleThreadpoolAnalysis.sleep(500);
return 0;
}
private int run_1() {
SimpleThreadpoolAnalysis.sleep(1000);
return 1;
}
private int run_2() {
SimpleThreadpoolAnalysis.sleep(2000);
return 2;
}
private int run_3() {
SimpleThreadpoolAnalysis.sleep(3000);
return 3;
}
private int run_4() {
SimpleThreadpoolAnalysis.sleep(4000);
return 4;
}
private int run_5() {
SimpleThreadpoolAnalysis.sleep(5000);
return 5;
}
}
public static void main(final String[] args) {
final SimpleThreadpoolAnalysis sta = new SimpleThreadpoolAnalysis();
sta.runSupervisorThread(100, 60000); // will run for a minute, updating every 100ms
// this will run a in background thread, so if other threads are done, this will end automatically, too
final int cores = Runtime.getRuntime().availableProcessors();
final ExecutorService executor = Executors.newFixedThreadPool(cores 1);
final int totalPages = 10;
for (int i = 0; i < totalPages; i ) {
// my code:
// final Runnable runnable = new TestWorker(); // this would be the normal call
final Runnable runnable = new TestWorker(); // this is the checked version
// your code: enable this, comment out my line above
//final Runnable runnable = () -> { /* your bitmap work */ }; // use this, just like in your code
final Runnable checkedRunnable = () -> sta.runChecked(runnable); // this is the checked version
final Future<?> future = executor.submit(checkedRunnable);
// ... some more of your code ...
}
}
}
I hope this helps you a bit.
CodePudding user response:
Turns out the issue was I was closing the pdf page before recycling the bitmap.
page.close();
.....
pageBitmap.recycle();
I moved the page.close()
after recycling bitmap and the thread is no longer hanging up
pageBitmap.recycle();
page.close();