I am developing an application that delivers notifications to android and iOS devices. I am using basic scaling and have implemented logic (modifying this example) so an appropriate number of workers are active at a given time without using a resident instance.
public class NotificationWorkerServlet extends HttpServlet {
/**
*
*/
private static final long serialVersionUID = 1L;
private static final Logger log = Logger
.getLogger(NotificationWorkerServlet.class.getName());
private static final int MAX_WORKER_COUNT = 5;
private static final int MILLISECONDS_TO_WAIT_WHEN_NO_TASKS_LEASED = 2500;
private static final int TEN_MINUTES = (10 * 60 * 1000);
// Area of concern
private static SyncCounter counter;
/**
* Used to keep number of running workers in sync
*/
private class SyncCounter {
private int c = 0;
public SyncCounter(){
log.info("Sync counter instantiated");
}
public synchronized void increment() {
c++;
log.info("Increment sync counter, workers:" + c);
}
public synchronized void decrement() {
c--;
log.info("Decrement sync counter, workers:" + c);
}
public synchronized int value() {
return c;
}
}
/**
* Call made from module when notification was added to task queue
*/
#Override
protected void doPost(HttpServletRequest req, HttpServletResponse resp)
throws ServletException, IOException {
super.doPost(req, resp);
// Instantiate counter with first call
if(counter == null){
counter = new SyncCounter();
}
log.info("Starting to build workers");
for (int workerNo = counter.value(); workerNo < MAX_WORKER_COUNT; workerNo++) {
log.info("Starting thread for worker: " + workerNo);
// Get the current queue to check it's statistics
Queue notificationQueue = QueueFactory
.getQueue("notification-delivery");
if (notificationQueue.fetchStatistics().getNumTasks() > 30 * workerNo) {
counter.increment();
Thread thread = ThreadManager
.createBackgroundThread(new Runnable() {
#Override
public void run() {
try {
doPolling();
} catch (Exception e) {
e.printStackTrace();
}
}
});
thread.start();
} else {
break; // Current number of threads is sufficient.
}
}
resp.setStatus(HttpServletResponse.SC_OK);
}
/**
* poll the task queue and lease the tasks
*
* Wait for up to 10 minutes for tasks to be added to queue before killing
* tasks
*
*/
private void doPolling() {
log.info("Doing pulling");
try {
int loopsWithoutProcessedTasks = 0;
Queue notificationQueue = QueueFactory
.getQueue("notification-delivery");
NotificationWorker worker = new NotificationWorker(
notificationQueue);
while (!LifecycleManager.getInstance().isShuttingDown()) {
boolean tasksProcessed = worker.processBatchOfTasks();
ApiProxy.flushLogs();
if (!tasksProcessed) {
log.info("waiting for tasks");
// Wait before trying to lease tasks again.
try {
loopsWithoutProcessedTasks++;
// If worker hasn't had any tasks for 30 min, kill it.
if (loopsWithoutProcessedTasks >= (TEN_MINUTES / MILLISECONDS_TO_WAIT_WHEN_NO_TASKS_LEASED)) {
break;
} else {
// Else, wait and try again (to avoid tearing down
// useful Notification Senders)
Thread.sleep(MILLISECONDS_TO_WAIT_WHEN_NO_TASKS_LEASED);
}
} catch (InterruptedException e) {
log.info("Notification worker thread interrupted");
break;
}
} else {
log.info("processed batch of tasks");
loopsWithoutProcessedTasks = 0;
}
}
} catch (Exception e) {
log.warning("Exception caught and handled in notification worker: "
+ e.getLocalizedMessage());
} finally {
counter.decrement();
}
log.info("Instance is shutting down");
}
}
In a controlled testing scenario, it works just fine. However, I know static, mutable values are bad news in servlets where multiple users could potentially be connecting at the same time.
Has anyone done something similar and had issues with pushing multiple notifications to the same device, lost tasks or had idle tasks burning a hole in the bank?
Related
What is the best approach to check how long my worker threads have been running since it picked up a message for processing and then log an error message if it exceeds a threshold time limit. I presume that needs to be managed in the WorkerManager class.
My WorkerManager kick starts the worker threads
If there are messages from the provider, then the worker thread processes them by calling a service class.
If there are no messages then it goes to sleep for a brief period.
When my worker is processing the messages and if it takes more than say 5 minutes to process, then I want to generate a warn message but still let the worker thread continue processing.
Question
I want to constantly check if my worker threads are exceeding processing of the messages by 5 minutes, if they exceed the threshold time, then I want to log an error message but still let the worker thread continue as is.
WorkerManager Class
public class WorkerManager implements Runnable {
private MyWorker[] workers;
private int workerCount;
private boolean stopRequested;
public WorkerManager(int count){
this.workerCount = count;
}
#Override
public void run(){
stopRequested = false;
boolean managerStarted = false;
while (!stopRequested) {
if(!managerStarted) {
workers = new MyWorker[workerCount];
for (int i = 0; i < workerCount; i++) {
final Thread workerThread = new Thread(workers[i], "Worker-" + (i + 1));
workerThread.start();
}
managerStarted = true;
}
}
}
public void stop(){
stopRequested = true;
}
//Calll this
public void cleanUpOnExit() {
for(MyWorker w: workers){
w.setStopRequested();
}
}
}
Worker Class
public class MyWorker implements Runnable {
private final int WAIT_INTERVAL = 200;
private MyService myService;
private MyProvider myProvider;
private boolean stopRequested = false;
public MyWorker(MyService myService, MyProvider myProvider){
this.myService = myService;
this.myProvider = myProvider;
}
public void setStopRequested() {
stopRequested = true;
}
#Override
public void run() {
while (!stopRequested) {
boolean processedMessage = false;
List<Message> messages = myProvider.getPendingMessages();
if (messages.size() != 0) {
AdapterLog.debug("We have " + messages.size() + " messages");
processedMessage = true;
for (Message message : messages) {
processMessage(messages);
}
}
if (!(processedMessage || stopRequested)) {
// this is to stop the thread from spinning when there are no messages
try {
Thread.sleep(WAIT_INTERVAL);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
private void processMessage(Message messages){
myService.process(messages);
}
}
Your WorkerManager needs a way to determine when the last message for each worker has been processed. So the workers will need to keep track of the timestamp of the last processed message.
Then, your WorkerManager could check the timestamps of each worker and generate the warnings if needed. In order to check the workers using a given period, you could use an exectutor:
ScheduledExecutorService scheduledExecutorService = Executors.newSingleThreadScheduledExecutor();
scheduledExecutorService.scheduleAtFixedRate(this::checkTimeoutProcessingMessages, 5l, 5l, TimeUnit.SECONDS);
And you could check the times getting the timestamp from each worker:
public void checkTimeoutProcessingMessages() {
for (MyWorker worker : workers) {
long lastProcessed = worker.getLastProcessedMessageTimestamp();
long currentTimestamp = System.currentTimeMillis();
if (lastProcessed + 5000 > currentTimestamp) {
//warn message
}
}
}
Refreshing records from a DB. We either get an explicit notification to refresh, or poll every 60 seconds. No more than one refresh per second.
If a request comes in, it should queue an immediate refresh if one has not happened within one second. Otherwise, it should schedule a refresh for 1 second after the end of the last refresh, unless such a task is already scheduled for that time or sooner.
After one minute without an explicit refresh, the timer should kick in and refresh, in case notifications were not sent.
There may be a large number of notifications coming in (several hundred per second).
Refreshing can be done by a separate single thread.
What's an elegant way to design this?
Here's what I have, but it might lead to too many requests:
private NotificationCenter() {
recordFetchService = Executors.newSingleThreadScheduledExecutor();
recordFetchService.scheduleWithFixedDelay(refreshCommand, minTimeBetweenRefresh, maxTimeBetweenRefresh, TimeUnit.MILLISECONDS);
}
private void queueRefresh() {
// explicit refresh requested. Schedule a refreshCommand to fire immediately, unless that would break our contract
if (!pending.isDone() && pending.getDelay(TimeUnit.MILLISECONDS) < minTimeBetweenRefresh) {
// a refresh is already scheduled
} else {
pending = recordFetchService.schedule(refreshCommand, 0L, TimeUnit.MILLISECONDS);
}
}
With "hundreds of notifications per second" an AtomicBoolean comes to mind to switch state exactly once from "doing nothing" to "going to do something" and vice versa. Couple the "going to do something" state with a Semaphore and you have the option to determine the exact moment when "going to do something" takes place.
Below a (runnable) example implementation/design that combines the AtomicBoolean and Semaphore to refresh data regularly while using notifications. It is probably not the most elegant way, but I do think it accomplishes the goal in a relative straightforward manner.
import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicBoolean;
public class RefreshTask {
private static final long MIN_WAIT_MS = 100L;
private static final long MAX_WAIT_MS = 1000L;
private ScheduledExecutorService scheduler;
private ExecutorService executor;
private volatile boolean stopping;
private final Semaphore refreshLock = new Semaphore(0);
private final AtomicBoolean refreshing = new AtomicBoolean();
private volatile long lastRefresh;
public void start() {
stopping = false;
refreshing.set(true);
lastRefresh = System.currentTimeMillis();
executor = Executors.newSingleThreadExecutor();
executor.execute(new RefreshLoop());
scheduler = Executors.newSingleThreadScheduledExecutor();
}
public void stop() {
stopping = true;
if (executor != null) {
refreshLock.release();
scheduler.shutdownNow();
executor.shutdownNow();
}
}
/** Trigger a (scheduled) refresh of data. */
public void refresh() {
if (refreshing.compareAndSet(false, true)) {
final long dataAge = System.currentTimeMillis() - lastRefresh;
if (dataAge >= MIN_WAIT_MS) {
refreshLock.release();
// println("Refresh lock released.");
} else {
long waitTime = MIN_WAIT_MS - dataAge;
scheduler.schedule(new RefreshReleaser(), waitTime, TimeUnit.MILLISECONDS);
println("Refresh scheduled in " + waitTime + " ms.");
}
} else {
// println("Refresh already triggered.");
}
}
protected void refreshData() {
// Refresh data from database
println("DATA refresh");
}
class RefreshLoop implements Runnable {
#Override
public void run() {
while (!stopping) {
try {
refreshData();
} catch (Exception e) {
e.printStackTrace();
}
lastRefresh = System.currentTimeMillis();
refreshing.set(false);
try {
if (!refreshLock.tryAcquire(MAX_WAIT_MS, TimeUnit.MILLISECONDS)) {
if (!refreshing.compareAndSet(false, true)) {
// Unlikely state, but can happen if "dataAge" in the refresh-method is around MAX_WAIT_MS.
// Resolve the race-condition by removing the extra permit.
if (refreshLock.tryAcquire()) {
println("Refresh lock race-condition detected, removed additional permit.");
} else {
println("Refresh lock race-condition detected, but no additional permit found.");
}
}
println("Refreshing after max waiting time.");
} // else refreshing already set to true
} catch (InterruptedException ie) {
if (!stopping) {
ie.printStackTrace();
}
}
}
println("Refresh loop stopped.");
}
}
class RefreshReleaser implements Runnable {
#Override
public void run() {
if (refreshing.get()) {
refreshLock.release();
println("Scheduled refresh lock release.");
} else {
println("Programming error, scheduled refresh lock release can only be done in refreshing state.");
}
}
}
/* *** some testing *** */
public static void main(String[] args) {
RefreshTask rt = new RefreshTask();
try {
println("Starting");
rt.start();
Thread.sleep(2 * MIN_WAIT_MS);
println("Triggering refresh");
rt.refresh();
Thread.sleep(MAX_WAIT_MS + (MIN_WAIT_MS / 2));
println("Triggering refresh 2");
rt.refresh();
Thread.sleep(MIN_WAIT_MS);
} catch (Exception e) {
e.printStackTrace();
} finally {
rt.stop();
}
}
public static final long startTime = System.currentTimeMillis();
public static void println(String msg) {
println(System.currentTimeMillis() - startTime, msg);
}
public static void println(long tstamp, String msg) {
System.out.println(String.format("%05d ", tstamp) + msg);
}
}
My code is the following:
WorkPool.java
import java.util.LinkedList;
/**
* Class that implements a work pool based on the model of "replicated workers"
* Tasks introduced in the work pool are objects of type MRCTask
*
*/
public class WorkPool {
int nThreads; //total number of worker threads
int nWaiting = 0; //number of worker threads blocked waiting for a task
public boolean ready = false; //problem is finished
LinkedList<MRCTask> tasks = new LinkedList<MRCTask>();
/**
* Constructor for class WorkPool.
* #param nThreads - number of worker threads
*/
public WorkPool(int nThreads) {
this.nThreads = nThreads;
}
/**
* Function which tries to obtain a task from the work pool
* If there are not available tasks, the function hangs until
* a task can be given or until the problem is finished
* #return A task to solve or null if the problem is finished
*/
public synchronized MRCTask getWork() {
if (tasks.size() == 0) { //empty work pool
nWaiting++;
/*
* finish condition:
* there is no available task in the work pool and no worker
* is active
*/
if (nWaiting == nThreads) {
ready = true;
/* problem is finished, announcing all workers */
notifyAll();
return null;
} else {
while (!ready && tasks.size() == 0) {
try {
this.wait();
} catch(Exception e) {e.printStackTrace();}
}
if (ready)
/* work is done */
return null;
nWaiting--;
}
}
return tasks.remove();
}
/**
* Function which inserts a task in the work pool
* #param sp The task which must be introduced
*/
synchronized void putWork(MRCTask sp) {
tasks.add(sp);
/* announcing one of the waiting workers */
this.notify();
}
}
TestMain.Java
public class TestMain {
public static void main(String[] args) throws InterruptedException {
int nr_proc = Runtime.getRuntime().availableProcessors();
WorkPool wp = new WorkPool(nr_proc);
MRCTask.t = 0;
for(int i = 0; i < 10000; i++)
wp.putWork(new MRCTask());
Worker[] wrk = new Worker[nr_proc];
for(int i = 0; i < nr_proc; i++)
wrk[i] = new Worker(wp);
for(int i = 0; i < nr_proc; i++)
wrk[i].start();
for(int i = 0; i < nr_proc; i++)
wrk[i].join();
System.out.println(MRCTask.t);
}
}
class Worker extends Thread {
WorkPool wp;
public Worker(WorkPool wp) {
this.wp = wp;
}
void work(MRCTask ps) throws Exception {
ps.processTask();
}
public void run() {
while (true) {
MRCTask ps = wp.getWork();
if (ps == null)
break;
try {
work(ps);
} catch (Exception e) {
e.printStackTrace();
System.exit(0);
}
}
}
}
class MRCTask {
static int t;
public void processTask() {
t += 5;
}
}
When running this code, I normally expect to get 50000 as the answer. That only happens when nr_proc is 1. When nr_proc is 2 or 4(in my case, availableProcessor() returns 4), I get random values, like 49960, 49900, 49995 and sometimes even 50000.
Could you please tell me what is wrong?
Thanks in advance and sorry for this huge code!
PS: I have tried to modify the function processTask() as it follows:
public void processTask() {
synchronized(this) {
t += 5;
}
}
but, I still get same problem. I thought having synchronized portions would help, but they don't.
Your value t is static. So the way you tried to synchronize things, it was doomed to failure. You actually didn't synchronize anything because you have many different MRCTask instances (10000 in fact). Each one of them has a separate lock/monitor.
Try changing your code in MRCTask to this:
private static final Object LOCK = new Object();
public void processTask() {
synchronized (LOCK) {
t += 5;
}
}
This is different, now you use a shared lock.
See if you still get unpredictable/random results. You won't.
Previously you were just having a classical race condition.
Today I found a wierd problem. What I want is to check server availability (Particularly SSL checking) once application started and display proper message if server is down. This process should work in background and user is able to navigate the app if server has problem (app can works offline).
What I did is simple. In main activity I have
#Override
protected void onStart()
{
super.onStart();
// Check Internet connection
// Check Location sensor
// Check server accessibility
BackendCheck backendCheck = new BackendCheck(this);
if (!backendCheck.execute())
{
displayErrorDialog();
return;
}
}
This is BackendCheck class:
public class BackendCheck implements Callable<Boolean>
{
private static final String TAG = BackendCheck.class.getSimpleName();
// Thread sleep time
private static final int THREAD_SLEEP = 5000;
// Number of attempts to call an API in order to get response
private static final int MAX_ATTEMPT = 3;
// Current attempt
private int counter = 0;
// The url that should be used in order to get server response
private String mTestUrl;
// App mContext
private Context mContext;
// Server status
private boolean mServerStatus = false;
public BackendCheck(Context context)
{
this(context, "");
}
public BackendCheck(Context context, String url)
{
this.mTestUrl = url;
this.mContext = context;
}
public boolean execute()
{
// Check #mTestUrl and use Feature API if this variable is empty
if (TextUtils.isEmpty(mTestUrl))
{
mTestUrl = PassengerConstants.URL_BASE + mContext.getResources()
.getString(R.string.uri_feature_payments);
}
// Get ExecutorService from Executors utility class, thread pool size is 10
ExecutorService executor = Executors.newFixedThreadPool(10);
do
{
// Increment counter
counter++;
// Submit Callable tasks to be executed by thread pool
Future<Boolean> future = executor.submit(this);
try
{
// Break Do-While loop if server responded to request (there is no error)
if (!future.get())
{
mServerStatus = true;
break;
}
}
catch (InterruptedException e)
{
Logger.error(TAG, e.getMessage());
}
catch (ExecutionException e)
{
Logger.error(TAG, e.getMessage());
}
} while (counter < MAX_ATTEMPT);
// Shut down the executor service now
executor.shutdown();
// Return server status
return mServerStatus;
}
#Override
public Boolean call() throws Exception
{
// Sleep thread for a few seconds
Thread.sleep(THREAD_SLEEP);
try
{
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(mTestUrl);
Logger.debug(TAG, "Attempt (" + counter + "), try to check => " + mTestUrl);
HttpResponse httpResponse = client.execute(get);
int connectionStatusCode = httpResponse.getStatusLine().getStatusCode();
Logger.debug(TAG,
"Connection code: " + connectionStatusCode + " for Attempt (" + counter
+ ") of request: " + mTestUrl);
if (isServerError(connectionStatusCode))
{
return true;
}
}
catch (IllegalArgumentException e)
{
Logger.error(TAG, e.getMessage());
}
catch (Exception e)
{
Logger.error(TAG, e.getMessage());
}
return false;
}
/**
* Server status checker.
*
* #param statusCode status code of HTTP request
* #return True if connection code is 5xx, False otherwise.
*/
private static boolean isServerError(int statusCode)
{
return (statusCode >= HttpURLConnection.HTTP_INTERNAL_ERROR);
}
}
What happens is, When I launch the application splash screen displays. Then after a few seconds mainActivity runs (first code) then - since my server is down (for testing purposes) - I have black screen for 15 seconds (since I set MAX_ATTEMPT to 3 and have 5 seconds thread sleep) and after that I'm able to see UI of mainActivity and my error message.
I expect Callable<> should works in background and I see mainActivity after splashScreen without problem (black screen).
What you think? What problem might be? Thanks.
It would appear that you are executing the BackendCheck callable in the main thread.
Classes that extend Callable are usually executed via an ExecutorService which is a separate thread itself, thus it executes in the background. You may want to take a look at the Runnable interface or Thread if you'd like to run a separate thread to execute in the background that does not return a value. Calling the start method will cause the class to execute in a separate thread as indicated by the documentation:
When an object implementing interface Runnable is used to create a thread, starting the thread causes the object's run method to be called in that separately executing thread.
If you need to return some data at the end of execution I highly recommend an ExecutorService but you could probably also get away with using a FutureTask though I have less experience with that class. Hopefully that helps.
Okay, I just fixed my problem. 'njzk2' is right. The problem is future.get() which is running on or blocking main thread. I fixed the issue by doing a few changes.
First, I call my execute() method from a new thread. Therefore the whole of processing will be done in another thread.
I added new start() method in order to run it.
Add a listener in BackendCheck class and implemented it in my activity.
Since I want to display a dialog if server is down and I'm in another thread then runOnUiThread(runnable) uses to show the dialog in main thread.
This is my complete code for your reference.
In my activity:
#Override
protected void onStart()
{
super.onStart();
// Check Location sensor
// Check server accessibility
BackendCheck backendCheck = new BackendCheck(this);
backendCheck.setServerListener(new BackendCheck.BackendCheckListener()
{
#Override
public void onServerIsDown()
{
MainActivity.this.runOnUiThread(new Runnable() {
public void run() {
displayErrorDialog();
}
});
}
});
backendCheck.start();
}
And my BackendCheck class:
public class BackendCheck implements Callable<Boolean>
{
public interface BackendCheckListener
{
public void onServerIsDown();
}
private static final String TAG = BackendCheck.class.getSimpleName();
// Thread sleep time
private static final int THREAD_SLEEP = 5000;
// Number of attempts to call an API in order to get response
private static final int MAX_ATTEMPT = 3;
// Current attempt
private int counter = 0;
// The url that should be used in order to get server response
private String mTestUrl;
// App mContext
private Context mContext;
// Server status
private boolean mIsServerWorking = false;
// Server listener
private BackendCheckListener mListener;
public BackendCheck(Context context)
{
this(context, "");
}
public BackendCheck(Context context, String url)
{
this.mTestUrl = url;
this.mContext = context;
}
public void setServerListener (BackendCheckListener listener)
{
this.mListener = listener;
}
public void start()
{
Thread thread = new Thread()
{
#Override
public void run() {
boolean isServerWorking = execute();
if(!isServerWorking)
{
mListener.onServerIsDown();
}
}
};
thread.start();
}
private boolean execute()
{
// Check #mTestUrl and use Feature API if this variable is empty
if (TextUtils.isEmpty(mTestUrl))
{
mTestUrl = PassengerConstants.URL_BASE + mContext.getResources()
.getString(R.string.uri_feature_payments);
}
// Get ExecutorService from Executors utility class
ExecutorService executor = Executors.newFixedThreadPool(1);
do
{
// Increment counter
counter++;
// Submit Callable tasks to be executed by thread pool
Future<Boolean> future = executor.submit(this);
try
{
// Skip sleeping in first attempt
if(counter > 1)
{
// Sleep thread for a few seconds
Thread.sleep(THREAD_SLEEP);
}
// Break Do-While loop if server responded to request (there is no error)
if (!future.get())
{
mIsServerWorking = true;
break;
}
}
catch (InterruptedException e)
{
Logger.error(TAG, e.getMessage());
}
catch (ExecutionException e)
{
Logger.error(TAG, e.getMessage());
}
} while (counter < MAX_ATTEMPT);
// Try to shut down the executor service now
try
{
executor.shutdown();
executor.awaitTermination(THREAD_SLEEP, TimeUnit.MILLISECONDS);
}
catch (InterruptedException e)
{
Logger.error(TAG, e.getMessage());
}
// Return server status
return mIsServerWorking;
}
#Override
public Boolean call() throws Exception
{
try
{
HttpClient client = new DefaultHttpClient();
HttpGet get = new HttpGet(mTestUrl);
Logger.debug(TAG, "Attempt (" + counter + "), try to check => " + mTestUrl);
HttpResponse httpResponse = client.execute(get);
int connectionStatusCode = httpResponse.getStatusLine().getStatusCode();
Logger.debug(TAG,
"Connection code: " + connectionStatusCode + " for Attempt (" + counter
+ ") of request: " + mTestUrl);
if (isServerError(connectionStatusCode))
{
return true;
}
}
catch (IllegalArgumentException e)
{
Logger.error(TAG, e.getMessage());
}
catch (Exception e)
{
Logger.error(TAG, e.getMessage());
}
return false;
}
/**
* Server status checker.
*
* #param statusCode status code of HTTP request
* #return True if connection code is 5xx, False otherwise.
*/
private static boolean isServerError(int statusCode)
{
return (statusCode >= HttpURLConnection.HTTP_INTERNAL_ERROR);
}
}
I have a long operation running in the background, like uploading stuff, converting images, audio, video, etc.
I would like to stop/cancel them if the user requested to stop the operation altogether.
How can accomplish this? Is there a design pattern for this?
Note: Some of the running code can be canceled and some can't. How do I find a compromise around that?
EDIT: I should have said that I want the operation to stop immediately.
To summarize and extend on what Jon has said:
You should let the thread know that it should exit the loop (volatile flag).
You may interrupt() the thread if you want it to exit out of a blocking state.
You should handle the InterruptedException inside the run method.
You should exit gracefully when you're interrupted (i.e. finish up whatever you're doing and clean up).
Some code:
private volatile bool _running;// volatile guarantees that the flag will not be cached
public void kill(){_running = false;}
public void run()
{
while(_running)
{
try
{
DoWork(); // you may need to synchronize here
}
catch(InterruptedException e)
{
// Handle e
}
}
}
(I'm assuming you're already performing the background work in a separate thread.)
Basically, you keep a shared boolean flag which the UI thread can set and the background thread periodically reads. When the flag says "stop", you stop :)
Note that the flag should be volatile or you should use a lock in order to make sure that the background thread definitely "sees" a change written from the UI thread.
It's relatively crude and feels a bit "manual" but it means you don't risk instability through aborting half way through an operation, unlike approaches such as Thread.stop().
My 2 cents. A task that is cancelable. The cancelImpl() and runImpl() need to be implemented.
import java.util.concurrent.CancellationException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
public abstract class CancelableTask extends Observable<CancelableTask>
implements Runnable {
private volatile boolean isStarted = false;
private volatile boolean isCanceled = false;
private volatile boolean isSuccess = false;
private volatile Exception e;
private volatile AtomicBoolean doneLock = new AtomicBoolean(false);
protected final AtomicInteger progress = new AtomicInteger(0);
public CancelableTask() {
}
#Override
public final void run() {
try {
runUnsafe();
} catch (Exception e) {
Config.getLog().i("CancelableTask threw an exception", e);
}
}
public final void runUnsafe() throws Exception {
// Config.getLog().d("Running cancelable task: " + toString());
notifyObservers(this);
isStarted = true;
try {
if (!isCanceled) {
runImpl();
}
} catch (Exception e) {
// Note: Cancel may throw exception
if (doneLock.compareAndSet(false, true)) {
this.e = e;
notifyObservers(this);
clearObservers();
// Someone else should do something with the exception
// Config.getLog().i("Failed cancelable task: " + toString(), e);
throw e;
}
// Cancel got to the lock first but may NOT have yet changed the cancel flag.
// Must throw cancellation exception.
}
if (doneLock.compareAndSet(false, true)) {
isSuccess = true;
progress.set(100);
notifyObservers(this);
clearObservers();
// Config.getLog().d("Finished cancelable task: " + toString());
return;
}
// The task was canceled but the isCanceled may not have been set yet.
synchronized (doneLock) { // Waiting for the cancel to finish it's logic
}
// assert isCanceled; // Commented out because android crashes the app in assertion
// No need to notify here because cancel was already notified in
// cancel method.
// notifyObservers(this);
// Config.getLog().d("Already canceled task: " + toString());
throw new CancellationException("Canceled while running!");
}
protected abstract void runImpl() throws Exception;
protected void cancelImpl() {}
public final void cancel() {
synchronized (doneLock) {
if (doneLock.compareAndSet(false, true)) {
// Config.getLog().i("Canceling cancelable task: " + toString());
isCanceled = true;
cancelImpl();
notifyObservers(this);
clearObservers();
}
}
}
public final boolean isCanceled() {
return isCanceled;
}
public final boolean isSuccessful() {
return isSuccess;
}
public final boolean isDone() {
return doneLock.get();
}
public final boolean isStarted() {
return isStarted;
}
public final Exception getError() {
return e;
}
public int getProgress() {
return progress.get();
}
/**
* Observers will be cleared after the task is done but only after all of them are notified.
*/
#Override
public void addObserver(Observer<CancelableTask> observer) {
super.addObserver(observer);
}
// protected void incrementProgress(int value) {
// progress += value;
// }
}
There is the CancelableCollection as well:
import java.util.Collection;
import java.util.LinkedHashMap;
import java.util.Map.Entry;
import java.util.concurrent.CancellationException;
public class CancelableCollection extends CancelableTask {
private LinkedHashMap<CancelableTask, Integer> cancelables = new LinkedHashMap<CancelableTask, Integer>();
private volatile boolean normalizing;
private volatile State state = new State(null, 0, 0);
private boolean isOneFailsAll = true;
// public boolean isOneFailsAll() {
// return isOneFailsAll;
// }
public void setOneFailsAll(boolean isOneFailsAll) {
this.isOneFailsAll = isOneFailsAll;
}
public int getTotalWeight() {
Collection<Integer> values = cancelables.values();
int total = 0;
for (int weight : values) {
total += weight;
}
return total;
}
/**
* Adds and runs the cancelable
*
* #param cancelable
* #return
* #throws Exception
* if failed while running
* #throws CancellationException
* if already canceled
*/
public void add(CancelableTask cancelable, int relativeTime) {
if (cancelable == null) {
return;
}
cancelables.put(cancelable, relativeTime);
if (isCanceled()) {
throw new CancellationException("Canceled while running!");
}
if (isDone()) {
throw new RuntimeException(
"Cannot add tasks if the Cancelable collection is done running");
}
if (normalizing) {
throw new RuntimeException(
"Cannot add tasks if already started normalizing");
}
}
#Override
protected void runImpl() throws Exception {
normalizeProgress();
for (Entry<CancelableTask, Integer> entry : cancelables.entrySet()) {
int currentRelativeTime = entry.getValue();
CancelableTask currentTask = entry.getKey();
// Advance the state to the next one with the progress from the
// previous one.
state = new State(currentTask, currentRelativeTime, state.getProgress());
try {
currentTask.runUnsafe();
} catch (Exception e) {
if (isOneFailsAll) {
throw e;
}
Config.getLog().i("Task failed but continueing with other tasks", e);
}
}
state = new State(null, 0, 100);
}
private void normalizeProgress() {
normalizing = true;
int overall = 0;
for (Entry<CancelableTask, Integer> entry : cancelables.entrySet()) {
overall += entry.getValue();
}
double factor = overall == 0 ? 1 : (double)100 / overall;
for (Entry<CancelableTask, Integer> entry : cancelables.entrySet()) {
entry.setValue((int) (entry.getValue() * factor));
}
}
#Override
protected void cancelImpl() {
for (CancelableTask cancelable : cancelables.keySet()) {
cancelable.cancel();
}
}
#Override
public int getProgress() {
int progress = this.progress.get();
int stateProgress = state.getProgress();
this.progress.compareAndSet(progress, stateProgress); // Setting this value just for easier debugging. I has no meaning in CancelableCollection
return super.getProgress();
}
private static class State {
private CancelableTask currentTask;
private int currentRelativeTime;
private int progress;
public State(CancelableTask currentTask, int currentRelativeTime,
int progress) {
super();
this.currentTask = currentTask;
this.currentRelativeTime = currentRelativeTime;
this.progress = progress;
}
public int getProgress() {
return progress
+ (currentTask == null ? 0 : (int)(currentTask.getProgress()
* (double)currentRelativeTime / 100));
}
}
}
stop the thread or asynctask youre using or call this.finish