4

I am using ThreadPoolExecutor to execute multiple long running tasks in background, pool size of ThreadPoolExecutor is 4 so when more than 4 task are being added they are pushed on to queue and when one of 4 tasks gets complete one task is poped from queue for execution.

I want to know is there any way to access object of Runnable that are currently being executed and not in queue, i.e first 4 tasks.

Aim: I want to do this to get current status of task at any given point, with the help of mThreadPoolExecutor.getQueue() I am accessing tasks being queued and ready to execute, please suggest me way to access tasks those are currently being executed so that I can attach and remove listener/handler on it when ever required.

My Runnable class:

public class VideoFileUploadRunner implements Runnable {

    private final VideoFileSync mVideoFileSync;
    private final DataService dataService;

    private Handler handler;

    public VideoFileUploadRunner(VideoFileSync videoFileSync, DataService dataService) {
        this.mVideoFileSync = videoFileSync;
        this.dataService = dataService;

    }

    public int getPK()
    {
        return  mVideoFileSync.get_idPrimaryKey();
    }

    public void setHandler(Handler handler) {
        this.handler = handler;
    }

    @Override
    public void run() {
        try {

            if (mVideoFileSync.get_idPrimaryKey() < 0) {
                addEntryToDataBase();
            }
            updateStatus(VideoUploadStatus.IN_PROGRESS);
            FileUploader uploader = new FileUploader();
            updateStatus(uploader.uploadFile(mVideoFileSync.getVideoFile()));



        } catch (Exception e) {
            updateStatus(VideoUploadStatus.FAILED);
            e.printStackTrace();
        }
    }

    private void addEntryToDataBase() {
        int pk = dataService.saveVideoRecordForSync(mVideoFileSync);
        mVideoFileSync.set_idPrimaryKey(pk);
    }

    private void updateStatus(VideoUploadStatus status) {
        if (handler != null) {
            Message msg = new Message();
            Bundle b = new Bundle();
            b.putString(AppConstants.Sync_Status, status.toString());
            msg.setData(b);
            handler.sendMessage(msg);
        }
        dataService.updateUploadStatus(mVideoFileSync.get_idPrimaryKey(), status.toString());


    }
} 

In Task progress list view holder:

public void setData(VideoFileSync fileSync) {
        tvIso.setText(fileSync.getVideoFile().getISO_LOOP_EQUP());
        tvUnit.setText(fileSync.getVideoFile().getUnit());
        tvName.setText(fileSync.getVideoFile().getLocalPath());
        tvStatus.setText(fileSync.getCurentStatus().toString());
        addHandleForUpdate(fileSync);
    }

    private void addHandleForUpdate(VideoFileSync fileSync) {

        Handler.Callback callBack = new Handler.Callback() {
            @Override
            public boolean handleMessage(Message msg) {
                if(msg.getData()!=null)
                {
                    tvStatus.setText(msg.getData().getString(AppConstants.Sync_Status));

                }
                return false;
            }
        };
        mHadler = new Handler(Looper.getMainLooper(),callBack);

        VideoFileUploadRunner runner = VideoUploadManager.getInstance().getRunnerForSyncFile(fileSync);
        if(runner!=null)
        runner.setHandler(mHadler);
    }

in VideoUploadManager I have following method to return Runnable object, Here I want help so that I can return tasks being currently executed.

public synchronized VideoFileUploadRunner getRunnerForSyncFile(VideoFileSync fileSync) {
        Iterator<Runnable> itr = mThreadPoolExecutor.getQueue().iterator();
        while (itr.hasNext()) {
            VideoFileUploadRunner runner = (VideoFileUploadRunner) itr.next();
            if (runner.getPK() == fileSync.get_idPrimaryKey()) {
                return runner;
            }
        }
        return null;

    } 
Chetan Kinger
  • 15,069
  • 6
  • 45
  • 82
DCoder
  • 3,486
  • 7
  • 36
  • 68
  • *so that I can attach and remove listener/handler on it when ever required*. Can you elaborate on what you mean by this? – Chetan Kinger Feb 06 '17 at 15:25
  • I am developing mobile application in which I have one screen to show current status of tasks, user can close application and come back to check status, So when user is on screen I want to attach handler to the objects of runnable – DCoder Feb 06 '17 at 15:27
  • 3
    Instead of from the outside of the runnable trying to find the runnable through the executor and attach a listener to it.. From inside the runnables run method, bind to an external listener and unbind at the end of the method. Therefore you can post your events, subscribe & unsubscribe all within the runnable, and only the runnables currently active will post their updates. – Charlie Feb 06 '17 at 15:34
  • Using a `FutureTask` may help. Take a look at http://stackoverflow.com/questions/30789402/get-callable-from-threadpooltaskexecutor-or-cast-runnable-to-callable – bradimus Feb 06 '17 at 15:41
  • @DCoder See my answer. You can use a ThreadFactory to hold the Runnable references for you. Do leave a comment for further clarifications. – Chetan Kinger Feb 06 '17 at 15:53
  • @bradimus Does `FutureTask` allow you to check if it is running state? I believe it does have a `get` method with a timeout but that won't help in this particular case as the OP wants to know if the task is running. – Chetan Kinger Feb 06 '17 at 16:24
  • Possible duplicate of [How to access running threads inside ThreadPoolExecutor?](http://stackoverflow.com/questions/35571395/how-to-access-running-threads-inside-threadpoolexecutor) – Chetan Kinger Feb 06 '17 at 17:14
  • @Charlie That sounds like a wonderful idea and probably the best solution to this problem IMO. – Chetan Kinger Feb 06 '17 at 17:24

3 Answers3

1

The best way is to expose a synchronized variable holding the informations on currently executing tasks.

public MyTask implements Runnable {
    private String id;
    private Map<String, MyTask> mapTasks;

    public MyTask(String id, Map<String, MyTask> mapTasks) {
        this.id = id;
        this.mapTasks = mapTasks;
    }

    public void run() {
         synchronized(mapTasks) {
             mapTasks.put(id, this);
         }

         ...

         synchronized(mapTasks) {
             mapTasks.remove(id);
         }
    }
}


// Create a map of tasks
Map<String, MyTask> mapTasks = new HashMap<String, MyTask>();

// How to create tasks
MyTask myTask1 = new MyTask("task1", mapTasks);
MyTask myTask2 = new MyTask("task2", mapTasks);

executorService.execute(myTask1);
executorService.execute(myTask2);

....

And to print the list of tasks currently in execution:

public void printCurrentExecutingTasks(Map<String, MyTask> tasks) {
    for (String id: tasks.keySet()) {
        System.out.println("Executing task with id: " + id);
    }
}
Davide Lorenzo MARINO
  • 26,420
  • 4
  • 39
  • 56
0

My anwser focuses on the problem : "how to know which runnables are being executed".

This approach keeps a concurrent Set of the active Runnables :

private final Set<VideoFileUploadRunner> active = Collections.newSetFromMap(new ConcurrentHashMap<>());

And Runnables being submitted to the ThreadPoolExecutor should be decorated with a Runnable that updates this set :

class DecoratedRunnable implements Runnable {

    final VideoFileUploadRunner runnable;

    public DecoratedRunnable(VideoFileUploadRunner runnable) {
        this.runnable = runnable;
    }

    @Override
    public void run() {
        active.add(runnable); // add to set
        try {
            runnable.run();
        } finally {
            active.remove(runnable); // finally remove from set (even when something goes wrong)
        }
    }
}

So we can decorate the VideoFileUploadRunner instances prior to submitting them :

executorService.submit(new DecoratedRunnable(videoFileUploadRunner));

The method getRunnerForSyncFile would then simply be implemented like this :

public VideoFileUploadRunner getRunnerForSyncFile(VideoFileSync fileSync) {
    return active.stream()
            .filter(videoFileUploadRunner -> videoFileUploadRunner.getPK() == fileSync.get_idPrimaryKey())
            .findAny()
            .orElse(null);
}

Remark : as @Charlie comments, this is not the best way to attach a listener to a Runnable. You can request a message handler to be set from inside the VideoFileUploadRunner's run() method, or initialize such instances with a MessageHandler set, or use this decorating approach to keep it out of the VideoFileUploadRunner class.

bowmore
  • 10,842
  • 1
  • 35
  • 43
0

This answer is in relation to my comment above.

Instead of trying to find the runnable through the executor and attach a listener to it, bind a listener to the runnable when you create it and post your events to the listener from the runnable's executing code.

Only the currently active runnables will post their updates.

Here's an example.

Create an interface for your listener to implement. Your listener could be the thread pool executor, a private inner class, etc.

/** 
 * Callback interface to notify when a video upload's state changes 
 */
interface IVideoUploadListener {

    /**
     * Called when a video upload's state changes

     * @param pUploadId The ID of the video upload
     * @param pStatus The new status of the upload
     */
    void onStatusChanged(int pUploadId, VideoUploadStatus pStatus);
}

Create an enumeration for your status types (for example)

/**
 * Enum to hold different video upload states
 */
enum VideoUploadStatus {
    IN_PROGRESS,
    ADDED_TO_DB,
    FILE_UPLOADED,
    FINISHED,
    FAILED
}

Hold a reference of the listener in each Runnable.

public class VideoFileUploadRunner implements Runnable {

    private final IVideoUploadListener mUploadListener;
    private final VideoFileSync mVideoFileSync;
    private final DataService   mDataService;
    private Handler mHandler;

    // etc...
}

Pass an instance of the interface through the constructor

public VideoFileUploadRunner(IVideoUploadListener pUploadListener, VideoFileSync pVideoFileSync, DataService pDataService) {
    mUploadListener = pUploadListener;
    mVideoFileSync  = pVideoFileSync;
    mDataService    = pDataService;
}

In the run method, post updates to the listener as you see fit.

@Override
public void run() {
    mUploadListener.onStatusChanged(getPrimaryKey(), VideoUploadStatus.IN_PROGRESS);
    try {
        if (mVideoFileSync.get_idPrimaryKey() < 0) {
            addEntryToDataBase();
            mUploadListener.onStatusChanged(getPrimaryKey(), VideoUploadStatus.ADDED_TO_DB);
        }
        FileUploader uploader = new FileUploader();
        uploader.uploadFile(mVideoFileSync.getVideoFile());
        mUploadListener.onStatusChanged(getPrimaryKey(), VideoUploadStatus.FILE_UPLOADED);

        // Other logic here...

        mUploadListener.onStatusChanged(getPrimaryKey(), VideoUploadStatus.FINISHED);
    }

    catch (Exception e) {
        mUploadListener.onStatusChanged(getPrimaryKey(), VideoUploadStatus.FAILED);
        e.printStackTrace();
    }
}

Your listeners implementation of the onStatusChanged() method should be synchronised. This will help avoid erroneous results from race conditions.

private IVideoUploadListener mUploadListener = new IVideoUploadListener() {
    @Override
    public synchronized void onStatusChanged(int pUploadId, VideoUploadStatus pStatus) {
        Log.i("ListenerTag", "Video file with ID " + pUploadId + " has the status " + pStatus.toString());
    }
};
Charlie
  • 2,876
  • 19
  • 26