Topic: 请教SWT线程问题

  Print this page

1.请教SWT线程问题 Copy to clipboard
Posted by: dliang
Posted on: 2004-03-01 17:00

在使用SWT编程的时候,我发现SWT控件只能在本线程进行访问,其他线程不能访问。

在下面这段程序中,我用一个外部线程“UserSyn”访问主线程的“Text”控件,结果得到了“Invalid thread access”错误。请问有没有解决方法,使外部线程可以访问主线程的控件?请高手指点。

//Main.java
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.*;

public class Main {
  Text text = null;
  
  public Main() {
    Display display = new Display();
    Shell shell = new Shell(display);
    shell.setSize(200,60);
    
    text = new Text(shell,SWT.BORDER);
    text.setBounds(0,0,200,20);

    shell.open();
    
    Thread t = new Thread(new UserSyn());
    t.start();

    while (shell != null && !shell.isDisposed()) {
      if (!display.readAndDispatch())
        display.sleep();
    }
  }
  
  class UserSyn implements Runnable {
    public void run() {
      while (true) {
        synchronized (this) {
          try {
            Thread.sleep(1000);
            text.setText("time up"); //Invalid thread access
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

  public static void main(String[] args) {
    new Main();
  }
}

2.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: Jove
Posted on: 2004-03-01 17:05

试试Display类的以下方法
public void asyncExec(Runnable runnable)
public void syncExec(Runnable runnable)
public void timerExec(int milliseconds,Runnable runnable)

3.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: dliang
Posted on: 2004-03-01 17:49

谢谢,我是这样改的:


    final Runnable timer = new Runnable () {
        public void run () {
          synchronized (this) {
            try {
              text.setText(
                Integer.toString(count++));
            } catch (Exception e) {
              e.printStackTrace();
            }
          }
        }
      };

    while (shell != null && !shell.isDisposed()) {
      
      if (!display.readAndDispatch())
        display.sleep();
      else
        display.timerExec(500,timer);
    }


基本上可以定时触发了,CPU也占用很小,有时会受到窗口事件的影响。
但是"display.timerExec"的时间不能设太长,否则就没反应了。我用了500。
其实我的本意是使用“wait”,等外部触发,比如网络接收数据后,再刷新界面。现在看是不可能的了,只有定时去查询了。

是不是我的设计方法有问题?

4.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: Jove
Posted on: 2004-03-01 18:52

我也倾向于不要定时查询,在合适的时候再去修改界面

至于你的这个定时器的例子,我是这么实现的
import org.eclipse.swt.widgets.*;

public class CounterFrame {
  private static int counter;

  public static void main(String[] args) {
    final Display display = new Display();
    final Shell shell = new Shell();
    shell.setText("SWT Application");
    shell.open();
    new Thread(new Runnable() {
      public void run() {
        while (true) {
          try {
            Thread.sleep(2000);
          } catch (InterruptedException e) {}
          display.asyncExec(new Runnable() {
            public void run() {
              shell.setText("" + counter++);
            }
          });
        }
      }
    }).start();
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch())
        display.sleep();
    }
  }
}

5.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: dliang
Posted on: 2004-03-01 19:18

非常感谢,我感觉你的方法已经很理想了,时间可以随便设,而且还是内部的线程。现在只有用这样的方法了。

6.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: Jove
Posted on: 2004-03-01 19:29

可以再优化一下,避免无谓的对象生成

import org.eclipse.swt.widgets.*;

public class CounterFrame {
  private static int counter;

  public static void main(String[] args) {
    final Display display = new Display();
    final Shell shell = new Shell();
    shell.setText("Counter");
    shell.open();
    new Thread() {
      private Runnable cmd = new Runnable() {
        public void run() {
          shell.setText(String.valueOf(counter++));
        }
      };
      public void run() {
        while (true) {
          try {
            Thread.sleep(2000);
          } catch (InterruptedException e) {
            return;
          }
          display.asyncExec(cmd);
        }
      }
    }
    .start();
    while (!shell.isDisposed()) {
      if (!display.readAndDispatch())
        display.sleep();
    }
  }
}

7.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: dliang
Posted on: 2004-03-01 19:33

又改了一下,这样可以了。

//Main.java
import org.eclipse.swt.SWT;
import org.eclipse.swt.widgets.*;

public class Main {
  Text text = null;
  
  int count = 0;
  Object syn = new Object();
  
  public Main() {
    final Display display = new Display();
    final Shell shell = new Shell(display);
    shell.setSize(200,60);
    
    text = new Text(shell,SWT.BORDER);
    text.setBounds(0,0,200,20);

    new Thread(new Runnable() {
      public void run() {
        while (true) {
          try {
            synchronized (syn) {
              syn.wait();
            }
          } catch (InterruptedException e) {
          }
          display.asyncExec(new Runnable() {
            public void run() {
              shell.setText(String.valueOf(count++));
            }
          });
        }
      }
    }).start();
    
    new UserSyn();
    
    shell.open();
    
    while (shell != null && !shell.isDisposed()) {
      if (!display.readAndDispatch())
        display.sleep();
    }
  }
  
  class UserSyn extends Thread {
    public UserSyn() {
      start();
    }
    public void run() {
      while (true) {
        synchronized (this) {
          try {
            Thread.sleep(1000);
            synchronized (syn) {
              syn.notify();
            }
          } catch (InterruptedException e) {
            e.printStackTrace();
          }
        }
      }
    }
  }

  public static void main(String[] args) {
    new Main();
  }
}


这样就是外部触发了,非常感谢你的指点!

8.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: hotyaya
Posted on: 2004-03-05 22:33

关注中

9.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: kingwzb
Posted on: 2004-03-29 16:03

学习

10.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: kite
Posted on: 2004-03-29 18:08

其实,不用这么麻烦,你只需在构建你的线程时把要更新的控件作为参数传进去即可。

11.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: zwq00000
Posted on: 2004-04-27 17:01

麻烦呀,怎么被你们搞得这么复杂。

Display.getCurrent().AsyncExec(new Runnable(){
public void run(){
//在这里随便写点什么都可以了。比如 text1.setText(...);
}
});

其实看看SWT源码就都清楚了,这里建立的 Runnable 接口 的实例根本不会成为一个独立的线程,只是由Synchronizer调用其中的 run 方法而已。
虽然在这里多数是用来传递系统消息,不过你顺便做点别的,也不反对。
还是搬一点正宗的理论来吧,免得误人。
线程问题
当使用小窗口工具箱时,了解用于阅读和调度平台 GUI 事件的底层线程模型是很重要的。当在应用程序的代码中使用 Java 线程时,UI 线程的实现将影响应用程序必须遵循的规则。

本机事件调度
在任何 GUI 应用程序下面,不管它的语言或用户界面工具箱是什么,OS 平台都会检测 GUI 事件,并将它们放在应用程序事件队列中。尽管在不同的 OS 平台上机制稍微有些不同,但是基础是相似的。当用户单击鼠标、输入字符或者对窗口的外观进行处理时,OS 将生成应用程序 GUI 事件,例如,鼠标单击、击键或者窗口绘制事件。它确定哪个窗口和应用程序应当接收每个事件,并将它放置在应用程序的事件队列中。

任何窗口化的 GUI 应用程序的底层结构都是事件循环。应用程序进行初始化,然后启动循环,它只从队列中阅读 GUI 事件,并相应地作出反应。在处理其中一个事件时完成的任何工作必须快速地进行,以便让 GUI 系统可以响应用户。

应该在单独的线程中执行由用户界面事件触发的长时间操作,以便允许事件循环线程快速返回,并从应用程序的队列中访存下一个事件。然而,必须利用显式锁定和序列化来控制从其它线程访问小窗口和平台 API。未能遵循规则的应用程序可能会导致 OS 调用失败,更糟糕的是,可能会锁定整个 GUI 系统。

工具箱用户界面线程
使用 C 语言的本机 GUI 程序员相当熟悉使用平台事件循环的设计注意事项。但是,用 Java 编写的较高级别的小窗口工具箱通常试图通过隐藏平台事件循环来向应用程序开发者隐瞒用户界面线程问题。

实现此过程的常见方法是设置专用工具箱用户界面线程,以便读取和调度事件循环,并将事件送至由正在单独线程中运行的应用程序服务的内部队列中。这允许工具箱有足够的时间来响应操作系统,而不对应用程序处理事件的时间设置任何限制。应用程序必须仍然使用特殊的锁定技术来从它们的应用程序线程中访问用户界面代码,但是,它在整个代码中的完成是一致的,原因是所有应用程序代码正在非用户界面线程中运行。

尽管听起来好象可以“防止”应用程序发生用户界面线程问题,但是,实际上它将导致许多问题。

当 GUI 事件的时间取决于 Java 线程实现和应用程序性能时,就很难调试和诊断问题。

目前的 GUI 平台对事件队列执行了许多优化。常见的优化就是将连续的绘制事件折叠到队列中。每当必须重新绘制窗口的一部分时,就可以检查队列是否存在绘制事件重叠或尚未调度的冗余绘制事件。可以将这些事件合并到一个绘制事件中,从而使得闪烁较少出现并且执行应用程序的绘制代码也不会太频繁。如果小窗口工具箱正在将事件快速拉出队列,并将它们发送到内部队列中,则此优化将会失败。

改变开发者对线程模型的理解会导致程序员在使用其它语言和工具箱来为本机 GUI 系统编程时发生混淆。

SWT用户界面线程
SWT 遵循平台直接支持的线程模型。应用程序在它的主线程中运行事件循环,并直接从此线程中调度事件。这是应用程序的“UI 线程”。

注意:从技术上来说,UI 线程是创建显示的线程。实际上,此线程也是运行事件循环和创建小窗口的线程。
由于所有事件代码都是从应用程序的用户界面线程中触发的,因此,处理事件的应用程序代码可以自由地访问小窗口,并且不需要任何特殊技术就可以进行图形调用。然而,当响应某事件而执行长时间运行的操作时,应用程序负责创建计算线程的分支。

注意,对于所有来自非用户界面线程的调用,而这些调用又必须来自用户界面线程,SWT 将触发 SWTException。
SWT 应用程序的主线程(包括事件循环)为如下所示:

public static void main (String [] args) {
Display display = new Display ();
Shell shell = new Shell (display);
shell.open ();
// start the event loop. We stop when the user has done
// something to dispose our window.
while (!shell.isDisposed ()) {
if (!display.readAndDispatch())
display.sleep();
}
display.dispose ();
}

一旦创建了小窗口并且打开了 shell,应用程序就会读取和调度 OS 队列中的事件,直到除掉 shell 窗口为止。如果队列中没有事件可用,则说明显示进入睡眠状态,以使其它应用程序有机会运行。

注意:SWT 应用程序的最常见线程模型是在计算线程中运行单个用户界面线程并执行长时间操作。然而,SWT 不会限制开发者使用此模型。应用程序可以运行多个用户界面线程,而每个线程中都有单独的事件循环。
SWT 提供了特殊的访问方法来从后台线程中调用小窗口和图形代码。

执行非用户界面线程中的代码
希望从非用户界面线程中调用用户界面的应用程序必须提供调用用户界面代码的可运行程序。显示类中的 syncExec(Runnable) 和 asyncExec(Runnable) 方法用来在适当的时间在用户界面线程中执行这些可运行程序。

当非用户界面线程中的应用程序代码取决于用户界面代码的返回值时,应当使用 syncExec(Runnable),否则,需要确保可运行程序在返回到线程中之前能够完成运行。SWT 将在从应用程序的用户界面线程运行完可运行程序之前阻塞调用线程。例如,正在根据窗口的当前大小来计算某些内容的后台线程将要同步地运行代码以获取窗口的大小,然后继续它的计算。
当应用程序需要执行某些用户界面操作但是与继续之前需要完成的操作无关时,应当使用 asyncExec(Runnable)。例如,更新进度指示器或者重绘窗口的后台线程可以请求异步更新,并继续它的处理。在此情况下,后台线程的时间与可运行程序的执行之间没有承诺关系。
以下代码片段演示了使用这些方法的模式:

// do time-intensive computations
...
// now update the UI. We don't depend on the result,
// so use async.
Display.getDefault ().asyncExec (new Runnable () {
public void run() {
myWindow.redraw();
}
});
// now do more computations
...

工作台和线程
当您从头实现 SWT 应用程序时,线程规则是很清楚的,原因是由您控制事件循环的创建以及将应用程序中的计算线程分支的决定。

如果您正在将插件代码添加至工作台,则没有线程“幻数”隐藏在 JFace 或工作台代码中。规则是简单明了的:

工作台插件代码在工作台的用户界面线程中执行。
如果从工作台中接收到事件,则它始终是在工作台的用户界面线程中执行。
如果插件将计算线程分支,则在对工作台、JFace 或 SWT 调用任何 API 时,它必须使用显示 asyncExec(Runnable) 或 syncExec(Runnable) 方法。
工作台和 JFace API 调用不会检查调用者是否正在用户界面线程中执行。然而,对于从非用户界面线程执行的所有 API 调用, SWT 将触发 SWTException。
如果插件使用 JFace IRunnableContext 接口来调用进度指示器和运行操作,则它会提供一个自变量来指定是否对计算线程分支以便运行操作。

12.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: longwang
Posted on: 2004-05-17 14:11

怎么翻得这么拗口?让我看得累呀。

总的看上去和swing的机制很像嘛。

13.Re:请教SWT线程问题 [Re: dliang] Copy to clipboard
Posted by: iforem
Posted on: 2004-05-30 12:08

这个主题很好
大家讨论得非常好啊

学习中~~~~~~~~~~~~~


   Powered by Jute Powerful Forum® Version Jute 1.5.6 Ent
Copyright © 2002-2021 Cjsdn Team. All Righits Reserved. 闽ICP备05005120号-1
客服电话 18559299278    客服信箱 714923@qq.com    客服QQ 714923