簡體   English   中英

一旦我將其傳遞給另一個線程中的Swing的invokeAndWait,就無法阻止Runnable

[英]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

當一堆東西被執行時,我們有以下情況

  1. currProcedure包含的第N運行theProcedure ,而
  2. 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應該運行,不是嗎? 必須有一個不歸路的地步。

我在這里假設在Futurecancel(true)最終作為底層線程上的.interrupt() (當前不確定,查找它)。 如果沒有,你肯定會找到一種方法以某種方式中斷doProcedure線程。

您的代碼在幾個方面被破壞了。 您似乎期望AtomicReference能夠神奇地修復您的競爭條件,但正如其名稱所示,它提供的所有內容都是對引用的原子訪問。 如果您多次訪問該對象,則您有多次訪問,每次訪問都是原子的,但仍然不是線程安全的。

第一個例子:

updater.set(new Updater());
try {
    SwingUtilities.invokeAndWait(updater.get());
} catch (InvocationTargetException | InterruptedException ex) {
}

set和以下get調用都是原子的,但誰說你將在getget的引用仍然是你之前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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM