私は安全に実際に多くのイベントを処理するからスレッドをブロックせずにイベントディスパッチスレッド(EDT)上で実行されているメソッドの実行をブロックすることができるようになりますバイナリセマフォを作成しようとしてきました。これは、最初は不可能に見えるかもしれませんが、Javaは、一部は内蔵されており、この機能に関連するが、私はかなり仕事にそれを得ることができません。
使用事例
あなたはEDTからモーダルスイングダイアログを表示する場合は、現在、(ダイアログが閉じられるまで、モーダルダイアログを表示し、あなたの方法は次の行に続行されませんので)EDTをブロックするように表示されますが、実際にいくつかのアンダーありますEDTは、モーダルダイアログが閉じられるまでディスパッチイベントに継続する新しいイベントループに入ります-hood魔法。
私のチームは現在、非常にゆっくりとJavaFXの(ややトリッキーなトランジション)にスイングから移行されたアプリケーションを持っていると私は示すことができ、モーダルダイアログを振るのと同じ方法でAWTイベントディスパッチスレッドからモーダルJavaFXのダイアログを表示できるようにしたかったです。これは、このユースケースを満たすために、おそらく道を他の用途のために便利になるでしょうEDT-安全なセマフォのいくつかの並べ替えを持っていることのように思えました。
アプローチ
java.awt.EventQueue.createSecondaryLoop()
作成するメソッドであるSecondaryLoop
あなたは、新しいイベント処理ループをキックオフするために使用できるオブジェクトを、。あなたが呼び出すとSecondaryLoop.enter()
、それは新しいイベントループ処理している間、呼び出しはブロックされます(そのノートコールのブロックを、しかしスレッドが、それはイベント処理ループで継続されているため、ブロックされていません)。あなたが呼び出すまで新しいイベントループは継続されますSecondaryLoop.exit()
(完全に真実ではないこと、私の見る関連のSOの質問)。
通常のスレッドのためのラッチを待機している、またはEDTのための二次ループに入るに取得結果への呼び出しをブロックセマフォを作成しましたので、私は。取得する各ブロックの呼び出しはまた、セマフォが解放されるときに呼び出されるブロック解除操作(通常のスレッドのため、それだけでラッチをデクリメントし、EDTのために、それは二次ループを抜け)を追加します。
ここに私のコードは次のとおりです。
import java.awt.EventQueue;
import java.awt.SecondaryLoop;
import java.awt.Toolkit;
import java.util.Stack;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Semaphore;
@SuppressWarnings("serial")
public class EventDispatchThreadSafeBinarySemaphore extends Semaphore{
/** Operations used to unblock threads when a semaphore is released.
* Must be a stack because secondary loops have to be exited in the
* reverse of the order in which they were entered in order to unblock
* the execution of the method that entered the loop.
*/
private Stack<Runnable> releaseOperations = new Stack<>();
private boolean semaphoreAlreadyAcquired = false;
public EventDispatchThreadSafeBinarySemaphore() {
super(0);
}
@Override
public boolean isFair() {
return false;
}
@Override
public void acquire() throws InterruptedException {
Runnable blockingOperation = () -> {};
synchronized(this) {
if(semaphoreAlreadyAcquired) {
//We didn't acquire the semaphore, need to set up an operation to execute
//while we're waiting on the semaphore and an operation for another thread
//to execute in order to unblock us when the semaphore becomes available
if(EventQueue.isDispatchThread()) {
//For the EDT, we don't want to actually block, rather we'll enter a new loop that will continue
//processing AWT events.
SecondaryLoop temporaryAwtLoop = Toolkit.getDefaultToolkit().getSystemEventQueue().createSecondaryLoop();
releaseOperations.add(() -> temporaryAwtLoop.exit());
blockingOperation = () -> {
if(!temporaryAwtLoop.enter()) {
//I don't think we'll run into this, but I'm leaving this here for now for debug purposes
System.err.println("Failed to enter event loop");
}
};
}
else {
//Non-dispatch thread is a little simpler, we'll just wait on a latch
CountDownLatch blockedLatch = new CountDownLatch(1);
releaseOperations.add(() -> blockedLatch.countDown());
blockingOperation = () -> {
try {
blockedLatch.await();
} catch (InterruptedException e) {
//I'll worry about handling this better once I have the basics figured out
e.printStackTrace();
}
};
}
}
else {
semaphoreAlreadyAcquired = true;
}
}
//This part must be executed outside of the synchronized block so that we don't block
//the EDT if it tries to acquire the semaphore while this statement is blocked
blockingOperation.run();
}
@Override
public void release() {
synchronized(this) {
if(releaseOperations.size() > 0) {
//Release the last blocked thread
releaseOperations.pop().run();
}
else {
semaphoreAlreadyAcquired = false;
}
}
}
}
そして、ここに私の関連するJUnitテストコードは(私はこれは私がこれまでに出てくることができました最小の最小検証例であり、大きなサイズをお詫び申し上げます)です。
public class TestEventDispatchThreadSafeBinarySemaphore {
private static EventDispatchThreadSafeBinarySemaphore semaphore;
//See https://stackoverflow.com/questions/58192008/secondaryloop-enter-not-blocking-until-exit-is-called-on-the-edt
//for why we need this timer
private static Timer timer = new Timer(500, null);
@BeforeClass
public static void setupClass() {
timer.start();
}
@Before
public void setup() {
semaphore = new EventDispatchThreadSafeBinarySemaphore();
}
@AfterClass
public static void cleanupClass() {
timer.stop();
}
//This test passes just fine
@Test(timeout = 1000)
public void testBlockingAcquireReleaseOnEDT() throws InterruptedException {
semaphore.acquire();
CountDownLatch edtCodeStarted = new CountDownLatch(1);
CountDownLatch edtCodeFinished = new CountDownLatch(1);
SwingUtilities.invokeLater(() -> {
//One countdown to indicate that this has begun running
edtCodeStarted.countDown();
try {
semaphore.acquire();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
//This countdown indicates that it has finished running
edtCodeFinished.countDown();
});
//Ensure that the code on the EDT has started
edtCodeStarted.await();
assertEquals("Code on original AWT event thread should still be blocked", 1, edtCodeFinished.getCount());
//Ensure that things can still run on the EDT
CountDownLatch edtActiveCheckingLatch = new CountDownLatch(1);
SwingUtilities.invokeLater(() -> edtActiveCheckingLatch.countDown());
//If we get past this line, then we know that the EDT is live even though the
//code in the invokeLater call is blocked
edtActiveCheckingLatch.await();
assertEquals("Code on original AWT event thread should still be blocked", 1, edtCodeFinished.getCount());
semaphore.release();
//If we get past this line, then the code on the EDT got past the semaphore
edtCodeFinished.await();
}
//This test fails intermittently, but so far only after the previous test was run first
@Test(timeout = 10000)
public void testConcurrentAcquiresOnEDT() throws InterruptedException {
int numThreads =100;
CountDownLatch doneLatch = new CountDownLatch(numThreads);
try {
semaphore.acquire();
//Queue up a bunch of threads to acquire and release the semaphore
//as soon as it becomes available
IntStream.range(0, numThreads)
.parallel()
.forEach((threadNumber) ->
SwingUtilities.invokeLater(() -> {
try {
semaphore.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
finally {
semaphore.release();
//Count down the latch to indicate that the thread terminated
doneLatch.countDown();
}
})
);
semaphore.release();
doneLatch.await();
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
問題
testConcurrentAcquiresOnEDT
時々合格し、時には失敗します。私はなぜ知っていると信じています。私は、Javaソースコードに掘って中WaitDispatchSupport
(の具体的なインプリメンテーションSecondaryLoop
と呼ばれるフラグまで)、ループは、基本的にイベントをディスパッチ継続keepBlockingEDT
クリアされます。これは、イベントの間、これをチェックします。私が呼び出すとexit
、そのフラグをクリアし、それがより多くのイベントを待っていた場合にはイベントキューをウェイクアップするイベントを送信します。しかし、それは発生しませんenter()
すぐに終了する方法(と私はそれはおそらくできたとにかくないと思います)。
だからここにどのようにデッドロックの結果です:
- メインスレッドがセマフォを取得し、
- EDTのスレッドがセマフォを取得しようとするが、それはすでにので、取得されています。
- 新しい2次ループを作成します。
- Runnableを、新たな二次ループを終了することを作成し、それをプッシュし
releaseOperations
、スタック - この最後のステップは、の必要性外によるものであること(ブロックに実行させる、二次ループに入るメモ
synchronized
ブロック
- メインスレッドは、以下が起こることを原因とセマフォを解放します:
releaseOperations
スタックはポップさと、それを呼び出しているexit
2次ループにexit
コールは、セットは、keepBlockingEDT
その二次ループのフラグをfalseに設定します
- 戻るEDTで、それだけでチェックを行ってしまった
keepBlockingEDT
(これがfalseに設定された右の前に)、それは次のイベントをフェッチしているフラグ。 - それはそれを取得しようとするので、それは、次のイベントがセマフォ上のブロックという別の実行可能であることが判明します
- これは別のものを作成し
SecondaryLoop
、元の上にSecondaryLoop
、それを入力します - この時点で、元は
SecondaryLoop
すでにそれの持っていたkeepBlockingEDT
フラグがクリアされ、それが現在秒を実行しているブロックされていることを除いては、ブロックを停止することができるだろうSecondaryLoop
。二つ目はSecondaryLoop
、これまで誰もが実際にセマフォが今取得していないので、それに呼ばれるの出口を持っている、それゆえ私たちは永遠にブロックされません。
私は数日のためにこれに取り組んできましたし、私が思い付くすべてのアイデアは行き止まりです。
私は単純に(別のスレッドがそれを取得しようとすると、私はIllegalStateExceptionがスローされます)複数のスレッドが一度にセマフォでブロックすることを許可しないことです可能な部分的な解決を、持っていると信じています。私はまだ、彼らそれぞれが独自のセマフォを使用する場合は、複数の二次ループは行くかもしれないが、各セマフォは、最大で1 2次ループを作成します。私はこれがうまくいくと(主に私はイベントスレッドからの単一JavaFXのモーダルダイアログを表示したいので)それがうまく私の最も可能性の高いユースケースを満たすことになると思います。私はちょうど私が近くにかなりクールなものを作るようになったように私は感じるので、他の誰かが他のアイデアを持っていたかどうかを知りたかったが、それだけでかなりの作業を行いません。
あなたが任意のアイデアを持っているなら、私に教えてください。そして、「私はかなり確信して、これは不可能であり、ここで、なぜだよ...」だけでなく、許容可能な答えがあります。
使用すると、Semaphore
ほとんどの場合、正しいアプローチではありません。何が欲しいのメカニズムをブロック使用しないで、ネストされたイベントループを入力することです。APIを読んでから、また、あなたが上に複雑なものです表示されます。ここでも、あなたが必要とするすべては1つのUIスレッド上で、ネストされたイベントループに入り、他のUIスレッドがその作業を完了すると、そのループを終了することです。私は以下のが要件を満たしていると考えています:
import java.awt.EventQueue;
import java.awt.SecondaryLoop;
import java.awt.Toolkit;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Supplier;
import javafx.application.Platform;
import javax.swing.SwingUtilities;
public class Foo {
public static <T> T getOnFxAndWaitOnEdt(Supplier<? extends T> supplier) {
Objects.requireNonNull(supplier, "supplier");
if (!EventQueue.isDispatchThread()) {
throw new IllegalStateException("current thread != EDT");
}
final SecondaryLoop loop = Toolkit.getDefaultToolkit()
.getSystemEventQueue()
.createSecondaryLoop();
final AtomicReference<T> valueRef = new AtomicReference<>();
Platform.runLater(() -> {
valueRef.set(supplier.get());
SwingUtilities.invokeLater(loop::exit);
});
loop.enter();
return valueRef.get();
}
public static <T> T getOnEdtAndWaitOnFx(Supplier<? extends T> supplier) {
Objects.requireNonNull(supplier, "supplier");
if (!Platform.isFxApplicationThread()) {
throw new IllegalStateException(
"current thread != JavaFX Application Thread");
}
final Object key = new Object();
final AtomicReference<T> valueRef = new AtomicReference<>();
SwingUtilities.invokeLater(() -> {
valueRef.set(supplier.get());
Platform.runLater(() -> Platform.exitNestedEventLoop(key, null));
});
Platform.enterNestedEventLoop(key);
return valueRef.get();
}
}
JavaFXの等価内部方法があるけれども方法はJavaFXの9で追加された8理由は、使用されるラムダ式内で使用される場合、ローカル変数は、最終又は最終効果なければならないためです。しかし、別のスレッドが、私はにより提供されるボラティリティの意味とは思わないが通知されている方法によるものとのメソッド厳密に必要とされているが、私は念のために、これらのメソッドを使用しましたが。Platform#enterNestedEventLoop
Platform#exitNestedEventLoop
AtomicReference
#get()
#set(T)
AtomicReference
ここで表示するように上記の使用例を示しますモーダルJavaFXのダイアログからイベントディスパッチスレッド:
Optional<T> optional = Foo.getOnFxAndWaitOnEdt(() -> {
Dialog<T> dialog = new Dialog<>();
// configure dialog...
return dialog.showAndWait();
});
上記ユーティリティメソッドから通信するためにあるイベントディスパッチスレッドにJavaFXのアプリケーションスレッドとその逆。阻止しなければならないそうでない場合は、ネストされたイベントループが必要であるUIスレッドのいずれかを入力し、それが関連したUIを凍結するだろう理由はここにあります。その結果を待っている間に、あなたが非UIスレッドとUIスレッド上でアクションを実行するために必要にしている場合はソリューションは、はるかに簡単です:
// Run on EDT
T result = CompletableFuture.supplyAysnc(/*Supplier*/, SwingUtilities::invokeLater).join();
// Run on FX thread
T result = CompletableFuture.supplyAsync(/*Supplier*/, Platform::runLater).join();
呼び出しjoin()
ので、必ずどちらかのUIスレッドのからメソッドを呼び出すことがないことが呼び出し元のスレッドをブロックします。