6

I've written a Scala program that I'd like to be triggered through a UI (also in Swing). The problem is, when I trigger it, the UI hangs until the background program completes. I got to thinking that the only way to get around this is by having the program run in another thread/actor and have it update the UI as and when required. Updating would include a status bar which would show the file currently being processed and a progressbar.

Since Scala actors are deprecated, I'm having a tough time trying to plough through Akka to get some kind of basic multithreading running. The examples given on the Akka website are also quite complicated.

But more than that, I'm finding it difficult to wrap my head around how to attempt this problem. What I can come up with is:

  1. Background program runs as one actor
  2. UI is the main program
  3. Have another actor that tells the UI to update something

Step 3 is what is confounding me. How do I tell the UI without locking up some variable somewhere?

Also, I'm sure this problem has been solved earlier. Any sample code for the same would be highly appreciated.

Suma
  • 33,181
  • 16
  • 123
  • 191
Plasty Grove
  • 2,807
  • 5
  • 31
  • 42
  • To start your Scala program from the UI, make sure that your program does not run on the EDT. `SwingWorker` can ease the work for that matter, or you can use `Executors`. Check out "Concurrency in Swing": http://stackoverflow.com/tags/swing/info – Guillaume Polet Mar 04 '13 at 14:28
  • Highly related http://stackoverflow.com/questions/13516752/using-opengl-with-akka-actors-guaranteeing-a-single-thread-is-used-for-a-specif – om-nom-nom Mar 04 '13 at 14:29
  • See [`How SwingWorker works`](http://stackoverflow.com/a/11546203/597657). – Eng.Fouad Mar 04 '13 at 15:07
  • Thanks for the suggestions on SwingWorker. I'm fairly new to Swing, and SwingWorker in Scala has been deprecated as of 2.10. I'll have to try to use javax.SwingWorker – Plasty Grove Mar 05 '13 at 07:41
  • If you think your answer is different enough from the answers you received, please, extract your answer as an answer. If you think it is not different enough, I suggest deleting it. It does not belong to the question body. – Suma Feb 27 '19 at 10:47
  • As there is no response from you, I am taking the answer out on my own, encouraged by https://meta.stackoverflow.com/questions/289344/how-to-treat-an-old-question-which-had-an-answer-edited-into-it discussing a situation like this. – Suma Feb 28 '19 at 09:01

4 Answers4

7

For scala 2.10

You can use scala.concurrent.future and then register a callback on completion. The callback will update the GUI on the EDT thread.

Lets do it!

//in your swing gui event listener (e.g. button clicked, combo selected, ...)
import scala.concurrent.future
//needed to execute futures on a default implicit context
import scala.concurrent.ExecutionContext.Implicits._ 


val backgroundOperation: Future[Result] = future {
    //... do that thing, on another thread
    theResult
}

//this goes on without blocking
backgroundOperation onSuccess {
    case result => Swing.onEDT {
        //do your GUI update here
    }
}

This is the most simple case:

  1. we're updating only when done, with no progress
  2. we're only handling the successful case

To deal with (1) you could combine different futures, using the map/flatMap methods on the Future instance. As those gets called, you can update the progress in the UI (always making sure you do it in a Swing.onEDT block

//example progress update
val backgroundCombination = backgroundOperation map { partial: Result =>
    progress(2)
    //process the partial result and obtain
    myResult2
} //here you can map again and again

def progress(step: Int) {
    Swing.onEDT {
        //do your GUI progress update here
    }
}

To deal with (2) you can register a callback onFailure or handle both cases with the onComplete.

For relevant examples: scaladocs and the relevant SIP (though the SIP examples seems outdated, they should give you a good idea)

pagoda_5b
  • 7,333
  • 1
  • 27
  • 40
  • Thanks for the detailed code! I'll try it out tonite when I get home and let you know how it goes – Plasty Grove Mar 05 '13 at 07:43
  • Please do, and let us know if you find any problem or issue – pagoda_5b Mar 05 '13 at 13:48
  • Sorry for the late reply. Futures works perfectly for me, I didn't know they even existed! Took some time to figure it out, but it finally worked for me, thanks to your answer. I did it very similarly, called the background process in a future, moved the required UI components to global variables and used futures to update them through the background process using Swing.EDT – Plasty Grove Mar 10 '13 at 05:53
5

If you want to use Actors, following may work for you.

There are two actors:

  • WorkerActor which does data processing (here, there is simple loop with Thread.sleep). This actor sends messages about progress of work to another actor:
  • GUIUpdateActor - receives updates about progress and updates UI by calling handleGuiProgressEvent method

UI update method handleGuiProgressEvent receives update event. Important point is that this method is called by Actor using one of Akka threads and uses Swing.onEDT to do Swing work in Swing event dispatching thread.

You may add following to various places to see what is current thread.

println("Current thread:" + Thread.currentThread())

Code is runnable Swing/Akka application.

import akka.actor.{Props, ActorRef, Actor, ActorSystem}
import swing._
import event.ButtonClicked

trait GUIProgressEventHandler {
  def handleGuiProgressEvent(event: GuiEvent)
}

abstract class GuiEvent

case class GuiProgressEvent(val percentage: Int) extends GuiEvent
object ProcessingFinished extends GuiEvent


object SwingAkkaGUI extends SimpleSwingApplication with GUIProgressEventHandler {

  lazy val processItButton = new Button {text = "Process it"}
  lazy val progressBar = new ProgressBar() {min = 0; max = 100}

  def top = new MainFrame {
    title = "Swing GUI with Akka actors"

    contents = new BoxPanel(Orientation.Horizontal) {
      contents += processItButton
      contents += progressBar
      contents += new CheckBox(text = "another GUI element")
    }

    val workerActor = createActorSystemWithWorkerActor()

    listenTo(processItButton)

    reactions += {
      case ButtonClicked(b) => {
        processItButton.enabled = false
        processItButton.text = "Processing"
        workerActor ! "Start"
      }

    }

  }

  def handleGuiProgressEvent(event: GuiEvent) {
    event match {
      case progress: GuiProgressEvent  => Swing.onEDT{
        progressBar.value = progress.percentage
      }
      case ProcessingFinished => Swing.onEDT{
        processItButton.text = "Process it"
        processItButton.enabled = true
      }
    }

  }

  def createActorSystemWithWorkerActor():ActorRef = {
    def system = ActorSystem("ActorSystem")

    val guiUpdateActor = system.actorOf(
      Props[GUIUpdateActor].withCreator(new GUIUpdateActor(this)), name = "guiUpdateActor")

    val workerActor = system.actorOf(
      Props[WorkerActor].withCreator(new WorkerActor(guiUpdateActor)), name = "workerActor")

    workerActor
  }


  class GUIUpdateActor(val gui:GUIProgressEventHandler) extends Actor {
    def receive = {
      case event: GuiEvent => gui.handleGuiProgressEvent(event)
    }
  }


  class WorkerActor(val guiUpdateActor: ActorRef) extends Actor {
    def receive = {
      case "Start" => {
        for (percentDone <- 0 to 100) {
            Thread.sleep(50)
            guiUpdateActor ! GuiProgressEvent(percentDone)
        }
      }
      guiUpdateActor ! ProcessingFinished
    }
  }

}
Arnost Valicek
  • 2,418
  • 1
  • 18
  • 14
  • 2
    Thanks so much for this. It is so difficult to do anything with akka and actors for a newbie because of the lack (or excess) of documentation and also because there are different versions of the API. For now, I believe Futures does it exactly the way I want. I now feel Actors is a bit of an overkill for a UI – Plasty Grove Mar 10 '13 at 05:51
  • Nice one, thank you. FYI, I had to use `Props(classOf[GuiUpdateActor], this)` and `Props(classOf[WorkerActor], guiUpdateActor)`, respectively, as the first argument to each invocation of `system.actorOf()` (akka version 2.3-M2). – Adam Mackler Dec 30 '13 at 19:07
0

If you need something simple you can run the long task in a new Thread and just make sure to update it in the EDT:

  def swing(task: => Unit) = SwingUtilities.invokeLater(new Runnable {
     def run() { task }
  })
  def thread(task: => Unit) = new Thread(new Runnable {
     def run() {task}
  }).run()

  thread({
    val stuff = longRunningTask()
    swing(updateGui(stuff))
  })
Garrett Hall
  • 29,524
  • 10
  • 61
  • 76
0

You can define your own ExecutionContext which will execute anything on the Swing Event Dispatch Thread using SwingUtilities.invokeLater and then use this context to schedule a code which needs to be executed by Swing, still retaining the ability to chain Futures the Scala way, including passing results between them.

  import javax.swing.SwingUtilities
  import scala.concurrent.ExecutionContext

  object OnSwing extends ExecutionContext {
    def execute(runnable: Runnable) = {
      SwingUtilities.invokeLater(runnable)
    }
    def reportFailure(cause: Throwable) = {
      cause.printStackTrace()
    }
  }
      case ButtonClicked(_)  =>
        Future {
          doLongBackgroundProcess("Timestamp")
        }.foreach { result =>
          txtStatus.text = result
        }(OnSwing)
Suma
  • 33,181
  • 16
  • 123
  • 191