アップロード、画像、音声、動画の変換など、バックグラウンドで長い操作を実行しています。ユーザーが操作を完全に停止するように要求した場合は、それらを停止/キャンセルしたいと思います。
どうすればこれを達成できますか?このためのデザインパターンはありますか?
注:実行中のコードの一部はキャンセルできますが、キャンセルできないものもあります。その周りの妥協点を見つけるにはどうすればよいですか?
編集:私は操作をすぐに停止したいと言ったはずです。
アップロード、画像、音声、動画の変換など、バックグラウンドで長い操作を実行しています。ユーザーが操作を完全に停止するように要求した場合は、それらを停止/キャンセルしたいと思います。
どうすればこれを達成できますか?このためのデザインパターンはありますか?
注:実行中のコードの一部はキャンセルできますが、キャンセルできないものもあります。その周りの妥協点を見つけるにはどうすればよいですか?
編集:私は操作をすぐに停止したいと言ったはずです。
ジョンが言ったことを要約して拡張するには:
interrupt()
、スレッドを使用できます。InterruptedException
内部を処理する必要があります。run
いくつかのコード:
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
}
}
}
(私はあなたがすでに別のスレッドでバックグラウンド作業を実行していると仮定しています。)
基本的にboolean
、UIスレッドが設定できる共有フラグを保持し、バックグラウンドスレッドが定期的に読み取ります。フラグが「停止」と表示されたら、停止します:)
バックグラウンドスレッドがUIスレッドから書き込まれた変更を確実に「認識」するようにするには、フラグを揮発性にするか、ロックを使用する必要があることに注意してください。
これは比較的粗雑で、少し「手動」のように感じますが、などのアプローチとは異なり、操作の途中で中止しても不安定になるリスクがないことを意味しますThread.stop()
。
私の2セント。キャンセル可能なタスク。およびcancelImpl()
をrunImpl()
実装する必要があります。
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;
// }
}
CancelableCollectionもあります。
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));
}
}
}
this.finishを使用するか、this.finishを使用して、スレッドまたは非同期タスクを停止します。