创建事件调度线程安全信号量

我一直在尝试创建一个二进制信号量,它能够安全地阻止在事件调度线程(EDT)上运行的方法的执行,而不会实际阻止线程处理更多事件。乍一看这似乎是不可能的,但 Java 有一些与此相关的内置功能,但我无法完全让它工作。

使用案例

目前,如果您从 EDT 显示模态摆动对话框,它似乎会阻止 EDT(因为显示模态对话框的方法在对话框关闭之前不会继续到下一行),但实际上有一些问题-hood 魔法,使 EDT 进入一个新的事件循环,该循环将继续调度事件,直到模式对话框关闭。

我的团队目前的应用程序正在非常缓慢地从 swing 迁移到 JavaFX(这是一个有点棘手的过渡),并且我希望能够以与显示 swing 模态对话框相同的方式从 AWT 事件调度线程显示模态 JavaFX 对话框。似乎拥有某种 EDT 安全信号量就可以满足这个用例,并且可能在以后的其他用途中派上用场。

方法

java.awt.EventQueue.createSecondaryLoop()是一种创建SecondaryLoop对象的方法,然后您可以使用该对象来启动新的事件处理循环。当您调用 时SecondaryLoop.enter(),调用将在处理新的事件循环时阻塞(请注意,调用会阻塞,但线程不会阻塞,因为它正在事件处理循环中继续)。新的事件循环将继续,直到您调用为止SecondaryLoop.exit()(这并不完全正确,请参阅我的相关 SO 问题)。

因此,我创建了一个信号量,其中阻塞调用会导致等待正常线程的锁存器,或者进入 EDT 的辅助循环。每个对 acquire 的阻塞调用还会添加一个在释放信号量时调用的解锁操作(对于普通线程,它只是递减锁存器,对于 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);

    }

米琪卡哇伊
浏览 102回答 1
1回答

撒科打诨

使用 aSemaphore很可能不是正确的方法。您想要的是进入嵌套事件循环,而不是使用阻塞机制。从阅读 API 来看,你似乎把事情过于复杂化了。同样,您所需要的只是在一个 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 {&nbsp; &nbsp; public static <T> T getOnFxAndWaitOnEdt(Supplier<? extends T> supplier) {&nbsp; &nbsp; &nbsp; &nbsp; Objects.requireNonNull(supplier, "supplier");&nbsp; &nbsp; &nbsp; &nbsp; if (!EventQueue.isDispatchThread()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new IllegalStateException("current thread != EDT");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; final SecondaryLoop loop = Toolkit.getDefaultToolkit()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .getSystemEventQueue()&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; .createSecondaryLoop();&nbsp; &nbsp; &nbsp; &nbsp; final AtomicReference<T> valueRef = new AtomicReference<>();&nbsp; &nbsp; &nbsp; &nbsp; Platform.runLater(() -> {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; valueRef.set(supplier.get());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; SwingUtilities.invokeLater(loop::exit);&nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; &nbsp; &nbsp; loop.enter();&nbsp; &nbsp; &nbsp; &nbsp; return valueRef.get();&nbsp; &nbsp; }&nbsp; &nbsp; public static <T> T getOnEdtAndWaitOnFx(Supplier<? extends T> supplier) {&nbsp; &nbsp; &nbsp; &nbsp; Objects.requireNonNull(supplier, "supplier");&nbsp; &nbsp; &nbsp; &nbsp; if (!Platform.isFxApplicationThread()) {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; throw new IllegalStateException(&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; "current thread != JavaFX Application Thread");&nbsp; &nbsp; &nbsp; &nbsp; }&nbsp; &nbsp; &nbsp; &nbsp; final Object key = new Object();&nbsp; &nbsp; &nbsp; &nbsp; final AtomicReference<T> valueRef = new AtomicReference<>();&nbsp; &nbsp; &nbsp; &nbsp; SwingUtilities.invokeLater(() -> {&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; valueRef.set(supplier.get());&nbsp; &nbsp; &nbsp; &nbsp; &nbsp; &nbsp; Platform.runLater(() -> Platform.exitNestedEventLoop(key, null));&nbsp; &nbsp; &nbsp; &nbsp; });&nbsp; &nbsp; &nbsp; &nbsp; Platform.enterNestedEventLoop(key);&nbsp; &nbsp; &nbsp; &nbsp; return valueRef.get();&nbsp; &nbsp; }}JavaFX 9 中添加了和方法,尽管 JavaFX 8 中也有等效的内部方法。使用的原因是因为在 lambda 表达式内部使用时,局部变量必须是 Final 或有效的 Final Platform#enterNestedEventLoop。然而,由于通知单独线程的方式,我不认为 和方法提供的易变性语义是严格需要的,但我使用了这些方法以防万一。Platform#exitNestedEventLoopAtomicReference#get()#set(T)AtomicReference以下是使用上述内容从事件调度线程显示模式 JavaFX 对话框的示例:Optional<T> optional = Foo.getOnFxAndWaitOnEdt(() -> {&nbsp; &nbsp; Dialog<T> dialog = new Dialog<>();&nbsp; &nbsp; // configure dialog...&nbsp; &nbsp; return dialog.showAndWait();});上述实用程序方法用于从事件调度线程到JavaFX 应用程序线程进行通信,反之亦然。这就是为什么需要输入嵌套事件循环,否则 UI 线程之一将必须阻塞,从而冻结关联的 UI。如果您位于非 UI 线程上并且需要在等待结果时在 UI 线程上运行操作,则解决方案要简单得多:// Run on EDTT result = CompletableFuture.supplyAysnc(/*Supplier*/, SwingUtilities::invokeLater).join();// Run on FX threadT result = CompletableFuture.supplyAsync(/*Supplier*/, Platform::runLater).join();调用join()将阻塞调用线程,因此请确保不要从任一 UI 线程调用该方法。
打开App,查看更多内容
随时随地看视频慕课网APP

相关分类

Java