0

So I'm actually not sure of what the problem really is. It propably relates to the way I'm handling my threads but I don't know how to fix this.

The program is a simple boid simulation with different "tribed" boids, and I want to have it so that when I make a new tribe it has it's own panel in the GUI. Now this works perfectly before I start the thread but after that it always freezes. I know that Swing is not thread safe but I'm not sure how to fix this problem.

Here's the code for adding the panels:

  val tribeBoxPanels = Buffer.empty[TribeBoxPanel]
  val tribeFrames = Buffer.empty[TribeSettingFrame]
  val addTribeFrame = ChooseTribeFrame.frame
  val addFlockingFrame = AddFlockingFrame.frame

  def addTribe(tribe: Tribe) = {
    pause()
    tribeFrames += new TribeSettingFrame(tribe)
    tribeBoxPanels += new TribeBoxPanel(tribe)
    refcontents
  }

  private def refcontents = {
    top.optionPanel.contents.clear()
    top.optionPanel.contents += new BoxPanel(Orientation.Vertical) {
      tribeBoxPanels.foreach(contents += _.tribeBoxPanel)
    }
    top.optionPanel.contents += new BoxPanel(Orientation.Horizontal) {
      contents += top.addTribeButton
    }
    top.optionPanel.contents += new BoxPanel(Orientation.Horizontal) {
      contents += top.vectorDebugButton
    }
    pause()
  }

And heres the code for the (runnable) thread:

  private var running = true

  def pause() = {
    if (running) {
      running = false
      t.stop()
    }
    else {
      running = true
      t = new Thread(BoidSimulation)
      t.start()
    }
  }

  var t = new Thread(BoidSimulation)
  t.start()

Im trying to stop the thread while adding the tribe but that does not seem to work, the GUI still freezes. I also tried t.interrupt() (because that's the better way) but that didn't work also.

EDIT: Could my problem be that I am trying to call the method AddTribe from another object that is not GUI2D (the object the method is in). Maybe if I pasted all of my code to the GUI2D class it would work?

EDIT 2: Tried calling the method like this:

def invoke(tribe: Tribe) = new Runnable() { def run() = addTribe(tribe)  }

Didn't help though, the bar that I am trying to add stuff to still freezes.

I also tried printing out the thread that is calling the method and this is what I got:

Thread[Thread-2,5,main]                  <- println(t)

Thread[AWT-EventQueue-0,6,main]          <- This call works

Thread[AWT-EventQueue-0,6,main]          <- This call works

Thread[AWT-EventQueue-0,6,main]          <- This call works

Thread[AWT-EventQueue-0,6,main]          <- After this it freezes.

So the method is being called from the AWT thread but it still freezes the GUI. So the threading is not my problem?

Edit 3: I think I found my problem!! It's quite backwards actually. Because the method is called at a press of a button Swing was trying to complete code that is meant for the calculation thread, that is what caused it to freeze. Now I need to find out the excact opposite of SwingUtilities.invokelater()

Edit 4: Tried creating a new runnable that would run that code but it is somehow still called from the AWT thread. What is the cause of this and how do I get the code to run from the calculation thread?

class AddTribe(dist: Int, maxSpeed: Int, boidMass: Int, color: Color, behavior: Behavior, boidAmount: Int) extends Runnable {
    println(Thread.currentThread())
    def run() = BoidSimulation.addTribe(dist: Int, maxSpeed: Int, boidMass: Int, color: Color, behavior: Behavior, boidAmount: Int)
  }

case ButtonClicked(e) if (e == addButton) => {
        new AddTribe(distSlider.value, maxSpdSlider.value / 10, massSlider.value / 10, color, new Flocking(separationSlider.value, alignmentSlider.value, cohesionSlider.value), boidSlider.value).run()
RusinaRange
  • 341
  • 1
  • 4
  • 18
  • 2
    If the GUI is freezing then that's not because of the background thread, it's more likely you're accidentally doing some computation on the GUI thread (making it unable to e.g. respond to events). Try running `jstack` while the GUI is frozen, and then look at what the GUI thread is doing. – lmm May 05 '15 at 10:25
  • @lmm That's exactly what it was! I was trying to do the code meant for the calculation thread in the AWT thread. How can I call the method that I want from the calculation thread? If you look at my latest edits you can see what I allready tried. – RusinaRange May 06 '15 at 21:01

2 Answers2

3

The keys things to remember about Swing threads are that:

  • You should not do long-running calculations on the special AWT/Swing thread, or the GUI will freeze (because the Swing thread never gets a chance to process GUI events). Any activity triggered from the GUI (e.g. by an event handler on a button etc) will be running on the Swing thread by default. Put such activities into background threads if they will take a long time.
  • You must only update the GUI from the Swing thread. This typically means that background threads should use SwingUtilities.invokeLater() to add work to the queue for the Swing thread, or use the SwingWorker helper class.

See also the Java tutorials on these topics:

Update:

You can check what thread is running any given bit of code by adding debug statements such as:

println(Thread.currentThread)

or using debugger tools. It helps to give readable names to any threads that you create yourself, using the appropriate constructor.

DNA
  • 42,007
  • 12
  • 107
  • 146
  • Thanks for the answer. Yeah that is exactly why I have the other thread, to do all the calculations. Could it be that my problem is calling the method addTribe from another object? It works when it's in the root of BoidSimulation but is it called by GUI2D then? – RusinaRange May 05 '15 at 12:19
  • It's not the calling object that matters, as such - but the code in other objects may be running in a different thread. See my update on how to tell what thread is running a piece of code. – DNA May 05 '15 at 12:46
  • Thanks! Really helpfull tips! Doesn't work though, the side bar still freezes. I also tried the println(Thread.currentThread) method, I'll update my results on the post since it wont fit in this comment. – RusinaRange May 06 '15 at 20:09
  • This. It can not be stressed enough how important it is to use the Event Dispatch Thread for everything Swing. – phdoerfler May 06 '15 at 20:45
  • @phdoerfler Definetly, but that wasn't actually the problem. If you look at the edits my problem is actually that the program is calling the method from the AWT thread and that makes it get stuck. Any advice on how I could call the method from the calculation thread? I allready tried creating a third thread but somehow the method is still called from the AWT thread. – RusinaRange May 06 '15 at 20:55
  • You might want to consider either using Worker Threads provided by Swing or use Scalas Future to simplify things. – phdoerfler May 06 '15 at 20:59
  • thats not really possible since it's an ongoing simulation. With worker threads I would need to make a new one every frame and futures wouldn't just work. Thanks for the input though! – RusinaRange May 06 '15 at 21:07
0

Calling .run() on a Runnable directly will just run it on the current thread. The simplest way to run it separately is to create a new Thread and start() it:

case ButtonClicked(e) if (e == addButton) =>
    new Thread(new AddTribeRunnable(...)).start()

Starting a new thread for each time the button is pressed is somewhat inefficient though; you might prefer to e.g. use a ThreadPoolExecutorService to manage a pool of threads. Or for a more advanced/high-performance technique you could look at using akka Actors.

lmm
  • 17,386
  • 3
  • 26
  • 37