Java MIDI设置JavaFX标签文本时抛出IllegalStateException的原因与解决
MIDI消息更新JavaFX标签抛出IllegalStateException的原因与解决办法
问题描述
开发读取MIDI Control Change消息并更新JavaFX GUI的应用时,设置滑块值正常,但设置标签文本时抛出IllegalStateException,标签内容无法更新。测试代码如下:
transmitter.setReceiver(new Receiver() { @Override public void send(MidiMessage message, long timeStamp) { byte midiBytes[] = message.getMessage(); int status = midiBytes[0] & 0xf0; // midi CC message if(status == 0xb0) { // 这行代码正常工作 controller.testSlider.setValue(midiBytes[2]); // 这行代码触发错误 controller.testLabel.setText(String.valueOf(midiBytes[2])); } } //... });
报错信息:
Exception in thread "Java Sound MidiInDevice Thread" java.lang.IllegalStateException: Not on FX application thread; currentThread = Java Sound MidiInDevice Thread at javafx.graphics/com.sun.javafx.tk.Toolkit.checkFxUserThread(Toolkit.java:297) at javafx.graphics/com.sun.javafx.tk.quantum.QuantumToolkit.checkFxUserThread(QuantumToolkit.java:458) at javafx.graphics/javafx.scene.Parent$3.onProposedChange(Parent.java:474) at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:113) at javafx.base/com.sun.javafx.collections.VetoableListDecorator.setAll(VetoableListDecorator.java:108) at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.updateChildren(LabeledSkinBase.java:282) at javafx.controls/javafx.scene.control.skin.LabeledSkinBase.lambda$new$11(LabeledSkinBase.java:219) at javafx.controls/com.sun.javafx.scene.control.LambdaMultiplePropertyChangeListenerHandler.lambda$new$1(LambdaMultiplePropertyChangeListenerHandler.java:88) at javafx.base/javafx.beans.value.WeakChangeListener.changed(WeakChangeListener.java:86) at javafx.base/com.sun.javafx.binding.ExpressionHelper$SingleChange.fireValueChangedEvent(ExpressionHelper.java:181) at javafx.base/com.sun.javafx.binding.ExpressionHelper.fireValueChangedEvent(ExpressionHelper.java:80) at javafx.base/javafx.beans.property.StringPropertyBase.fireValueChangedEvent(StringPropertyBase.java:104) at javafx.base/javafx.beans.property.StringPropertyBase.markInvalid(StringPropertyBase.java:111) at javafx.base/javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:145) at javafx.base/javafx.beans.property.StringPropertyBase.set(StringPropertyBase.java:50) at javafx.base/javafx.beans.property.StringProperty.setValue(StringProperty.java:71) at javafx.controls/javafx.scene.control.Labeled.setText(Labeled.java:147) at midi.Midi$1.send(Midi.java:88) at java.desktop/com.sun.media.sound.AbstractMidiDevice$TransmitterList.sendMessage(AbstractMidiDevice.java:629) at java.desktop/com.sun.media.sound.MidiInDevice.callbackShortMessage(MidiInDevice.java:155) at java.desktop/com.sun.media.sound.MidiInDevice.nGetMessages(Native Method) at java.desktop/com.sun.media.sound.MidiInDevice.run(MidiInDevice.java:127) at java.base/java.lang.Thread.run(Thread.java:833)
原因分析
JavaFX的所有UI组件更新操作必须在FX应用线程中执行,而MIDI Receiver的send方法是在Java Sound MidiInDevice Thread后台线程中触发的,不属于FX应用线程。
滑块setValue暂时未报错,是因为Slider的数值属性修改没有立即触发严格的线程检查,但这属于未定义行为,实际开发中同样应该遵循FX线程规则,否则可能引发隐性问题。而Label的setText操作会直接触发UI布局更新,因此立即检测到线程违规并抛出异常。
解决方法
使用Platform.runLater()方法将UI更新代码包裹起来,把任务提交到FX应用线程执行。该方法会将传入的Runnable任务加入FX线程队列,等待合适时机执行。
修改后的代码如下:
import javafx.application.Platform; transmitter.setReceiver(new Receiver() { @Override public void send(MidiMessage message, long timeStamp) { byte midiBytes[] = message.getMessage(); int status = midiBytes[0] & 0xf0; // midi CC message if(status == 0xb0) { final int ccValue = midiBytes[2]; // 将所有UI更新操作放入FX应用线程执行 Platform.runLater(() -> { controller.testSlider.setValue(ccValue); controller.testLabel.setText(String.valueOf(ccValue)); }); } } //... });
额外说明
- 所有涉及JavaFX UI组件属性修改、布局调整的操作,都应该通过
Platform.runLater()切换到FX线程执行,避免线程安全问题。 - 如果有大量连续的MIDI消息,建议对任务进行合并,避免FX线程队列过载,影响UI响应速度。
内容的提问来源于stack exchange,提问作者Ernest
相关产品推荐
相关产品推荐

