开发者

How can I enable to stop MY running code?

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.

开发者_StackOverflow社区

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

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜