解决SWT主线程假死现象,研究SWT UI线程以及非UI线程关系。
消息循环
所谓“消息循环”,实际是程序循环。即Windows 中有一个系统消息队列,对于每一个正在执行的Windows应用程序,系统为其建立一个“消息队列”,即应用程序队列,用来存放该程序可能创建的各种窗口的消息。应用程序中含有一段称作“消息循环”的代码,用来从消息队列中检索这些消息并把它们分发到相应的窗口函数中。如果应用程序处理某个消息事件的时候,用了很长的时间,这时候后续的消息无法及时得到处理,就会造成应用程序没有响应,也就是常说的“假死”状态。 所以,应用程序如果处理某个事件需要较长的时间,需要把这个操作放到一个另外的线程中进行处理。
UI线程与非UI线程
SWT中主线程为UI线程
/**
* Launch the application.
* @param args
*/
public static void main(String args[]) {
try {
VoiceToText window = new VoiceToText();
window.setBlockOnOpen(true);
window.open();
Display.getCurrent().dispose();
} catch (Exception e) {
e.printStackTrace();
}
}
主线程中各种控件的操作函数也为UI线程
Button btnNewButton = new Button(container, SWT.NONE);
btnNewButton.setBounds(89, 59, 80, 27);
btnNewButton.setText("New Button");
btnNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// TODO Auto-generated method stub
for(int i=0; i<20; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
});
UI线程中不能执行阻塞操作,否则会出现假死状态
UI线程必须实时得到响应,否则会出现假死状态,例如执行如下程序:
btnNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// TODO Auto-generated method stub
for(int i=0; i<20; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
});
UI线程会出现假死状态
UI线程中耗时操作,应该启动非UI线程来执行耗时操作
将耗时操作放入到非UI线程中,则不会出现假死状态
btnNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0; i<40; i++){
try {
Thread.sleep(1000);
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}).start();
}
});
非UI线程中不能直接操作UI线程元素,否则会出现Invalid thread access
btnNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0; i<40; i++){
try {
Thread.sleep(1000);
btnNewButton.setText(String.valueOf(i));
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}).start();
}
});
非UI线程中操作UI线程元素应该使用Display.asyncExec或者syncExec方法
btnNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0; i<40; i++){
try {
Thread.sleep(1000);
parent.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
btnNewButton.setText(String.valueOf(k++));
}
});
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
}).start();
}
});
非UI线程中Display.asyncExec或者syncExec方法不能阻塞,否则会出现假死状态。因为非UI线程中Display.asyncExec或者syncExec方法是通过UI线程来执行实现的,应该若该方法阻塞,则UI线程会出现阻塞。
btnNewButton.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
// TODO Auto-generated method stub
new Thread(new Runnable() {
@Override
public void run() {
parent.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
for(int i=0; i<40; i++){
try {
Thread.sleep(1000);
parent.getDisplay().asyncExec(new Runnable() {
@Override
public void run() {
// TODO Auto-generated method stub
btnNewButton.setText(String.valueOf(k++));
}
});
} catch (InterruptedException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
}
}
});
}
}).start();
}
});
由以上可知,耗时操作应该放在非UI线程中,在非UI线程中操作UI线程元素,需要使用Display.asyncExec或者syncExec方法,并且该方法中不能执行耗时操作。
因此最终SWT程序设计如下:
开始转换的按钮逻辑如下:
//开始转换按钮
Button startThansfer = new Button(container, SWT.NONE);
startThansfer.setBounds(340, 185, 283, 27);
startThansfer.setText("开始转换");
startThansfer.addSelectionListener(new SelectionAdapter() {
@Override
public void widgetSelected(SelectionEvent e) {
logDetailText.append(DateUtils.getDataPrefix() + "开始转换........" + "\n");
voicePath = voicePathText.getText();
textPath = textPathText.getText();
outputPath = textPath + "\\" + System.currentTimeMillis() + ".txt";
new Thread(new TransferThreadRunnable(parent, logDetailText, voicePath, textPath, startThansfer, outputPath)).start();
}
});
非UI线程类的设计如下:
package com.voice.text;
import java.io.FileOutputStream;
import java.util.HashMap;
import org.eclipse.swt.widgets.Button;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Text;
import com.alibaba.fastjson.JSON;
import com.iflytek.msp.cpdb.lfasr.client.LfasrClientImp;
import com.iflytek.msp.cpdb.lfasr.exception.LfasrException;
import com.iflytek.msp.cpdb.lfasr.model.LfasrType;
import com.iflytek.msp.cpdb.lfasr.model.Message;
import com.iflytek.msp.cpdb.lfasr.model.ProgressStatus;
import com.iflytek.voicecloud.lfasr.demo.Test;
public class TransferThreadRunnable implements Runnable {
private Text logDetailText;
private LfasrType type = LfasrType.LFASR_STANDARD_RECORDED_AUDIO;
private int sleepSecond = 20;
private String voicePath;
private Display display;
private Button startThansfer;
private String outputPath;
public TransferThreadRunnable(Composite parent, Text logDetailText, String voicePath, String textPath, Button startThansfer, String outputPath) {
this.logDetailText = logDetailText;
this.voicePath = voicePath;
this.display = parent.getDisplay();
this.startThansfer = startThansfer;
this.outputPath = outputPath;
}
public void asycExecPrint(String log){
display.asyncExec(new Runnable() {
@Override
public void run() {
logDetailText.append(log);
}
});
}
public void asycExecSetStartThansferButtonStatus(boolean status){
display.asyncExec(new Runnable() {
@Override
public void run() {
startThansfer.setEnabled(status);
}
});
}
@Override
public void run() {
transfer();
}
private int transfer(){
asycExecSetStartThansferButtonStatus(false);
// 初始化LFASRClient实例
LfasrClientImp lc = null;
try {
lc = LfasrClientImp.initLfasrClient();
} catch (LfasrException e) {
// 初始化异常,解析异常描述信息
Message initMsg = JSON.parseObject(e.getMessage(), Message.class);
asycExecPrint(DateUtils.getDataPrefix() + "ecode = " + initMsg.getErr_no() + "\n");
asycExecPrint(DateUtils.getDataPrefix() + "failed = " + initMsg.getFailed() + "\n");
return -1;
}
// 获取上传任务ID
String task_id = "";
HashMap<String, String> params = new HashMap<String, String>();
params.put("has_participle", "true");
//合并后标准版开启电话版功能
//params.put("has_seperate", "true");
try {
// 上传音频文件
Message uploadMsg = lc.lfasrUpload(voicePath, type, params);
while(true){
// 判断返回值
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
asycExecPrint(DateUtils.getDataPrefix() + "文件正在上传......" + "\n");
int ok = uploadMsg.getOk();
if (ok == 0) {
// 创建任务成功
task_id = uploadMsg.getData();
asycExecPrint(DateUtils.getDataPrefix() + "创建任务成功 task_id = " + task_id + "\n");
break;
} else {
// 创建任务失败-服务端异常
asycExecPrint(DateUtils.getDataPrefix() + "ecode = " + uploadMsg.getErr_no() + "\n");
asycExecPrint(DateUtils.getDataPrefix() + "failed = " + uploadMsg.getFailed() + "\n");
return -1;
}
}
} catch (LfasrException e) {
// 上传异常,解析异常描述信息
Message uploadMsg = JSON.parseObject(e.getMessage(), Message.class);
asycExecPrint(DateUtils.getDataPrefix() + "ecode=" + uploadMsg.getErr_no() + "\n");
asycExecPrint(DateUtils.getDataPrefix() + "failed=" + uploadMsg.getFailed() + "\n");
return -1;
}
// 循环等待音频处理结果
while (true) {
try {
// 等待20s在获取任务进度
for(int i=0; i<sleepSecond/2; i++){
//每隔2秒打印一次当前状态
Thread.sleep(2000);
asycExecPrint(DateUtils.getDataPrefix() + "当前任务正在处理 " + "waiting ..." + "\n");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
try {
// 获取处理进度
Message progressMsg = lc.lfasrGetProgress(task_id);
// 如果返回状态不等于0,则任务失败
if (progressMsg.getOk() != 0) {
asycExecPrint(DateUtils.getDataPrefix() + "任务处理失败. task_id:" + task_id + "\n");
asycExecPrint(DateUtils.getDataPrefix() + "ecode=" + progressMsg.getErr_no() + "\n");
asycExecPrint(DateUtils.getDataPrefix() + "failed=" + progressMsg.getFailed() + "\n");
return -1;
} else {
ProgressStatus progressStatus = JSON.parseObject(progressMsg.getData(), ProgressStatus.class);
if (progressStatus.getStatus() == 9) {
// 处理完成
asycExecPrint(DateUtils.getDataPrefix() + "任务处理完成. task_id = " + task_id + "\n");
break;
} else {
// 未处理完成
asycExecPrint(DateUtils.getDataPrefix() + "任务没有处理完成,继续处理. task_id = " + task_id + ", status:" + progressStatus.getDesc() + "\n");
continue;
}
}
} catch (LfasrException e) {
// 获取进度异常处理,根据返回信息排查问题后,再次进行获取
Message progressMsg = JSON.parseObject(e.getMessage(), Message.class);
asycExecPrint(DateUtils.getDataPrefix() + "ecode = " + progressMsg.getErr_no() + "\n");
asycExecPrint(DateUtils.getDataPrefix() + "failed = " + progressMsg.getFailed() + "\n");
}
}
// 获取任务结果
try {
Message resultMsg = lc.lfasrGetResult(task_id);
// 如果返回状态等于0,则获取任务结果成功
if (resultMsg.getOk() == 0) {
// 打印转写结果
String result = Test.getFinalResult(resultMsg.getData());
FileOutputStream f;
try {
f = new FileOutputStream(outputPath);
f.write(result.getBytes());
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
asycExecPrint(DateUtils.getDataPrefix() + "结果存放路径: " + outputPath + "\n");
} else {
// 获取任务结果失败
asycExecPrint(DateUtils.getDataPrefix() + "ecode = " + resultMsg.getErr_no() + "\n");
asycExecPrint(DateUtils.getDataPrefix() + "failed = " + resultMsg.getFailed() + "\n");
return -1;
}
} catch (LfasrException e) {
// 获取结果异常处理,解析异常描述信息
Message resultMsg = JSON.parseObject(e.getMessage(), Message.class);
asycExecPrint(DateUtils.getDataPrefix() + "ecode=" + resultMsg.getErr_no() + "\n");
asycExecPrint(DateUtils.getDataPrefix() + "failed=" + resultMsg.getFailed() + "\n");
return -1;
}
asycExecSetStartThansferButtonStatus(true);
return 1;
}
}
代码位置:https://github.com/ChenWenKaiVN/VoiceToText/chen0327
问题记录:
三月 27, 2019 7:58:51 下午 org.jaudiotagger.tag.id3.AbstractID3v2Frame readIdentifier
警告: 格式工厂20171225佟麟阁中学访谈2.mp3:No space to find another frame:
三月 27, 2019 7:58:51 下午 org.jaudiotagger.tag.id3.ID3v23Tag readFrames
警告: 格式工厂20171225佟麟阁中学访谈2.mp3:Invalid Frame:格式工厂20171225佟麟阁中学访谈2.mp3:No space to find another frame
注意JDK版本1.8