[英]Can't stop a Runnable once I've passed it to Swing's invokeAndWait within a different thread
我有一個帶有固定線程池的類,我用它來多次運行一個過程。 部分過程涉及創建一個Runnable
,它被傳遞給SwingUtilities.invokeAndWait
以更新我的Swing GUI。
我希望能夠在發出再次運行過程的請求時搶占任何正在運行的過程。 為此,我將Future
保留在最后一次submit()
調用中,這樣我就可以cancel()
它。 我還保留了最后一個Runnable
所以我可以在它上面設置一個標志,告訴它在run()
時什么都不做,因為它可能已經傳遞給了AWT事件線程,因此我無法通過取消我的Future
來阻止它。
但是,無論我做什么, Runnable
都會被執行。 這是一個精簡的例子:
class Procedure {
private AtomicReference<Updater> updater;
private AtomicReference<Future<?>> currProcedure;
private ExecutorService threadPool;
private Runnable theProcedure;
public Procedure() {
updater = new AtomicReference<Updater>();
currProcedure = new AtomicReference<Future<?>>();
threadPool = Executors.newFixedThreadPool(1);
theProcedure = new Runnable() {
public void run() {
doProcedure();
}
};
}
private void doProcedure() {
// a bunch of stuff
updater.set(new Updater());
try {
SwingUtilities.invokeAndWait(updater.get());
} catch (InvocationTargetException | InterruptedException ex) {
}
// some more stuff
}
public void execute() {
try {
synchronized(this) {
if (null != currProcedure.get()) {
currProcedure.get().cancel(true);
}
if (null != updater.get()) {
updater.get().cancel();
}
}
currProcedure.set(threadPool.submit(theProcedure));
} catch (RejectedExecutionException ex) {
}
}
}
class Updater implements Runnable {
private AtomicBoolean cancelled;
public Updater() {
cancelled = new AtomicBoolean(false);
}
public void cancel() {
cancelled.set(true);
}
public void run() {
if (cancelled.get()) {
return;
}
// do the GUI update
}
}
它會像這樣使用:
Procedure p = new Procedure();
p.execute();
p.execute();
實際上,我從AWT事件線程調用Procedure.execute()
,所以我認為這可能與它有關; 除此之外,我不知道我做錯了什么或如何實現我的目標。 有幫助嗎?
編輯 :我還嘗試cancel()
我的Updater
關閉AWT事件線程,但無論如何都沒有運氣:
if (null != currProcedure.get()) {
currProcedure.get().cancel(true);
}
if (null != updater.get()) {
threadPool.submit(new Runnable() {
public void run() {
updater.get().cancel();
}
});
}
編輯2 :我的日志記錄似乎意味着我成功地中斷了updater
(我得到了一個java.lang.InterruptedException
)但是run()
仍然執行(並且cancelled.get()
在它的結尾仍然是false
的)。 為什么不cancel(true)
停止執行updater
?
對我來說,問題似乎是符合要求的
// a bunch of stuff
當一堆東西被執行時,我們有以下情況
currProcedure
包含的第N運行theProcedure
,而 updater
僅包含Updater
的(N-1)st實例。 因此,在synchronized(true)
塊中的兩個cancel
調用不會以匹配的項目對為目標。 事實上,(N-1)st Updater
被取消,但那個很長。 另一方面,“一堆東西”可能並不關心在Future
上調用的cancel(true)
,所以一旦完成,“一堆東西”之后的行就會被cancel(true)
並安排下一個Updater
。
要解決這個問題,以下內容可能有所幫助。 在“一堆東西”之后,輸入以下代碼:
synchronized(this) {
if (!Thread.currentThread().isInterrupted()) {
// copy the lines after "a bunch of stuff here to schedule the updater
}
}
在execute()
您無需費心取消Updater
。 它不需要任何取消功能。
您現在已將兩個synchronized
塊放入事件中。 新的execute()
要么首先進入同步塊。 然后,它可以搶占正在運行的doProcedure
調度Updater
,或者doProcedure
使其同步。 那么, Updater
應該運行,不是嗎? 必須有一個不歸路的地步。
我在這里假設在Future
上cancel(true)
最終作為底層線程上的.interrupt()
(當前不確定,查找它)。 如果沒有,你肯定會找到一種方法以某種方式中斷doProcedure
線程。
您的代碼在幾個方面被破壞了。 您似乎期望AtomicReference
能夠神奇地修復您的競爭條件,但正如其名稱所示,它提供的所有內容都是對引用的原子訪問。 如果您多次訪問該對象,則您有多次訪問,每次訪問都是原子的,但仍然不是線程安全的。
第一個例子:
updater.set(new Updater());
try {
SwingUtilities.invokeAndWait(updater.get());
} catch (InvocationTargetException | InterruptedException ex) {
}
set
和以下get
調用都是原子的,但誰說你將在get
上get
的引用仍然是你之前set
的引用? 例如,您可能會收到一個更新的Updater
實例,該實例由另一個線程調度,導致一個Updater
永遠不會發送到EDT但另一個發送到EDT的情況。
第二個例子:
if (null != currProcedure.get()) {
currProcedure.get().cancel(true);
}
if (null != updater.get()) {
updater.get().cancel();
}
相同的錯誤兩次。 您正在檢查AtomicReference.get()
的結果是否為null
但在該調用上是非null
,誰說它在下次調用時仍然是非null
? 您在synchronized
塊中具有該代碼,但由於其他線程訪問相同的變量,例如在沒有同步的情況下從doProcedure()
內部doProcedure()
,因此它不提供任何保護。 也許你永遠不會將引用重置為null
(這將是另一個錯誤)所以這里沒有問題,但它清楚地表明了對如何使用AtomicReference
的誤解。
此外,你說你正在使用它:
Procedure p = new Procedure();
p.execute();
p.execute();
所以你調用execute
后的另一項權利之一,這些執行方法嘗試取消任何他們在找到updater
立即但此時的背景很可能是執行你的“一堆東西”並沒有能夠成功地設定其updater
(如哈拉爾指出出 )。 因此,兩個execute
調用都可能看到null
或一個非常過時的Updater
實例, 然后后台線程設置第一個Updater
,然后在第二個Updater
,它們都沒有被取消。 請注意,后台線程的中斷會終止invokeAndWait
的等待部分,但不會取消提供的runnable的執行 。
由於您使用的是AtomicReference
您應該開始使用它。 原子更新是實現預期邏輯的關鍵特性,例如:
Updater newUpdater=new Updater();
Updater old=updater.getAndSet(newUpdater);
if(old!=null) old.cancel();
SwingUtilities.invokeLater(newUpdater);
通過讀取舊的更新程序並以原子方式設置新的更新程序,您可以確保每個新的Updater
與可能取消舊的Updater
配對。 通過將值保留在局部變量中而不是多次讀取它們,可以確保在更改引用之間不存在中間更新。 這甚至適用於多個后台線程,並且不需要額外的synchronized
塊。
調度Updater
的調用已更改為invokeLater
,因為在單線程執行程序等待Updater
完成的情況下,暗示線程永遠不會設置新的Updater
並取消舊的Updater
。
請注意, Updater
本身應在完成時將updater
引用設置為null
。 取消已經完成的Updater
是沒有意義的,但由於updater
引用是一個共享引用,它可能存在的時間遠遠超過Updater
的(預期)生命周期,因此應該將其清除,以便立即進行垃圾收集而不是說謊在下一個Updater
設置之前。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.