我写了一个Scala程序,我希望通过UI(也在Swing中)触发.问题是,当我触发它时,UI会挂起,直到后台程序完成.我认为解决这个问题的唯一方法是让程序在另一个线程/ actor中运行,并在需要时让它更新UI.更新将包括一个状态栏,显示当前正在处理的文件和进度条.
由于Scala演员已被弃用,我现在很难通过Akka来进行某种基本的多线程运行.Akka网站上给出的例子也很复杂.
但更重要的是,我发现很难绕过如何尝试这个问题.我能想到的是:
第3步让我感到困惑.如何在不锁定某个变量的情况下告诉UI?
此外,我确信这个问题已经解决了.任何示例代码都将受到高度赞赏.
对于scala 2.10
您可以scala.concurrent.future在完成时使用然后注册回调.回调将更新EDT线程上的GUI.
我们开始做吧!
//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
}
}
Run Code Online (Sandbox Code Playgroud)
这是最简单的情况:
要处理(1)您可以使用实例上的map/ flatMapmethods 组合不同的未来Future.当这些被调用时,您可以更新UI中的进度(始终确保您在Swing.onEDT块中执行此操作)
//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
}
}
Run Code Online (Sandbox Code Playgroud)
要处理(2)你可以注册回调onFailure或处理两种情况onComplete.
对于相关的例子:scaladocs和相关的SIP(虽然SIP示例似乎已经过时,但它们应该给你一个好主意)
如果您想使用Actors,以下内容可能适合您.
有两个演员:
UI更新方法handleGuiProgressEvent接收更新事件.重要的一点是,Actor使用一个Akka线程调用此方法,并使用Swing.onEDT在Swing事件调度线程中执行Swing工作.
您可以在各个地方添加以下内容以查看当前线程的内容.
println("Current thread:" + Thread.currentThread())
Run Code Online (Sandbox Code Playgroud)
代码是可运行的Swing/Akka应用程序.
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
}
}
}
Run Code Online (Sandbox Code Playgroud)