在RCP中要在非UI线程中执行UI线程的操作,最简单的方式就是display.syncExec或者display.asyncExec,如果UI线程所需的时间较长的话,则应该使用display.asyncExec
在执行异步线程的时候,我们一般应当继承Job或者UIJob类: UIJob是在UI线程中运行的,可以直接访问窗体组件。Job是在非UI线程中运行,如果在里面想访问窗体组件,需要通过Display.asynExec()或者synExec方式来执行。UIJob要尽量的短,不要过多的占用UI线程的时间。
在我现在的系统中,每当UI需要和Server交互的时候,都需要检测 Server 是否启动,如果未启动的话,尝试启动三次,三次不成功则抛出Exception,用MessageDialog给用户提示,启动成功以后,还需载入相关信息。这样的异步线程,就只能放到Job中执行。
然而,这样还是远远不够的,试想一下,如果点完一个MenuItem以后,过了很久才会有所反应,这种用户体验岂不是糟糕透了?于是我们还要提供进度条来提高用户体验度,在RCP中,就是如下的代码:
第二行中的 progress.setCancelable(true)是为了允许客户在长时间执行后台进程的时候,可以取消掉进程的执行。这里我把实际的执行过程Extract了出来,免得方法太长,doSomething()看起来应该是这样子的:
这样子当用户点击ProgressMonitor的Cancel按钮时, monitor.isCanceled()就会返回true,doSomething便中止执行。
说到这里,可能就会有疑问了,monitor.isCanceled()方法只会在doFirstThing()和doSecondThing()之间才会被触发,那么如果doFirstThing()的过程中有异常情况而导致无法返回,那点击Cancel就根本没有作用啊?
唔......这个就是问题的核心所在了,我不知道别人是如何解决的,在这里我只说一下我的解决方案,希望能够起到抛砖引玉的作用:
在前面已经提到,我使用了Job来处理异步线程,然而Job是通过调用job.schedule来执行的,用户无法确保job被执行的时机以及何时结束,一般的方法是使用Listener,Observer或者某个信号量来指示Job的结束。在有些地方我用的是Observer模式,而在这种情况下,我用的是boolean变量来做指示。于是,上面的doSomething就变成了:
这样,只要while循环没有满足结束条件,我们就可以通过点击Cancel按钮来cancel掉Job。如果job中还有些东西是无法自动cancel掉的话,比如Socket通信等,我们还可以在job.cancel()前面加上一些代码来做这样的工作,比如job.getSocket().close()等,当然还要处理好各种Exception。
在Eclipse的Article里面,有几篇分别讲述Job和ProgressMonitor的文章,很是详细,有兴趣的朋友不妨找来看看。不过文章只是讲解原理性的东西,开发中所碰到的问题,还是要靠个人的经验来分析解决......所以,还是多多coding,多多thinking,提高解决实际问题的能力吧:)
在执行异步线程的时候,我们一般应当继承Job或者UIJob类: UIJob是在UI线程中运行的,可以直接访问窗体组件。Job是在非UI线程中运行,如果在里面想访问窗体组件,需要通过Display.asynExec()或者synExec方式来执行。UIJob要尽量的短,不要过多的占用UI线程的时间。
在我现在的系统中,每当UI需要和Server交互的时候,都需要检测 Server 是否启动,如果未启动的话,尝试启动三次,三次不成功则抛出Exception,用MessageDialog给用户提示,启动成功以后,还需载入相关信息。这样的异步线程,就只能放到Job中执行。
然而,这样还是远远不够的,试想一下,如果点完一个MenuItem以后,过了很久才会有所反应,这种用户体验岂不是糟糕透了?于是我们还要提供进度条来提高用户体验度,在RCP中,就是如下的代码:
- ProgressMonitorDialog progress = new ProgressMonitorDialog(null);
- progress.setCancelable(true);
- try {
- progress.run(true, true, new IRunnableWithProgress() {
- public void run(IProgressMonitor monitor)
- throws InvocationTargetException {
- doSomething();
- }
- });
- } catch (InvocationTargetException e) {
- e.printStackTrace();
- } catch (InterruptedException e) {
- }
第二行中的 progress.setCancelable(true)是为了允许客户在长时间执行后台进程的时候,可以取消掉进程的执行。这里我把实际的执行过程Extract了出来,免得方法太长,doSomething()看起来应该是这样子的:
- public void doSomething(IProgressMonitor monitor) {
- monitor.beginTask("Beginning...", IProgressMonitor.UNKNOWN);
- monitor.subTask("Doing first job");
- doFirstThing();
- if(monitor.isCanceled()) return;
- monitor.worked(1);
- monitor.subTask("Doing second job");
- doSecondThing();
- if(monitor.isCanceled()) return;
- monitor.worked(2);
- ......
- monitor.done();
- }
- }
这样子当用户点击ProgressMonitor的Cancel按钮时, monitor.isCanceled()就会返回true,doSomething便中止执行。
说到这里,可能就会有疑问了,monitor.isCanceled()方法只会在doFirstThing()和doSecondThing()之间才会被触发,那么如果doFirstThing()的过程中有异常情况而导致无法返回,那点击Cancel就根本没有作用啊?
唔......这个就是问题的核心所在了,我不知道别人是如何解决的,在这里我只说一下我的解决方案,希望能够起到抛砖引玉的作用:
在前面已经提到,我使用了Job来处理异步线程,然而Job是通过调用job.schedule来执行的,用户无法确保job被执行的时机以及何时结束,一般的方法是使用Listener,Observer或者某个信号量来指示Job的结束。在有些地方我用的是Observer模式,而在这种情况下,我用的是boolean变量来做指示。于是,上面的doSomething就变成了:
- public void doSomething(IProgressMonitor monitor) {
- monitor.beginTask("Beginning...", IProgressMonitor.UNKNOWN);
- monitor.subTask("Doing first job");
- job.schedule();
- while (!jobFinished) {
- if (monitor.isCanceled()) {
- logger.info("monitor is canceled");
- job.cancel();
- return;
- }
- monitor.worked(times);
- times++;
- }
- monitor.done();
- }
- }
这样,只要while循环没有满足结束条件,我们就可以通过点击Cancel按钮来cancel掉Job。如果job中还有些东西是无法自动cancel掉的话,比如Socket通信等,我们还可以在job.cancel()前面加上一些代码来做这样的工作,比如job.getSocket().close()等,当然还要处理好各种Exception。
在Eclipse的Article里面,有几篇分别讲述Job和ProgressMonitor的文章,很是详细,有兴趣的朋友不妨找来看看。不过文章只是讲解原理性的东西,开发中所碰到的问题,还是要靠个人的经验来分析解决......所以,还是多多coding,多多thinking,提高解决实际问题的能力吧:)