2013-03-04 71 views
5

我编写了一个我想通过UI(也在Swing中)触发的Scala程序。问题是,当我触发它时,UI挂起直到后台程序完成。我不得不考虑解决这个问题的唯一方法是让程序在另一个线程/ actor中运行,并在需要时更新UI。更新将包括一个状态栏,它将显示当前正在处理的文件和一个进度条。使用Swing进行异步UI更新

由于Scala演员已被弃用,我正在试图通过Akka来获取某种基本多线程运行的艰难时间。阿卡网站上的例子也很复杂。

但更重要的是,我发现很难围绕如何解决这个问题。我能想出是:

  1. 后台程序运行作为一个演员
  2. UI是主程序
  3. 有另一个演员,告诉UI更新的东西

第3步什么让我混淆。如何在不锁定某个变量的情况下告诉UI?

此外,我相信这个问题已经解决了。任何示例代码相同将不胜感激。

=======================

编辑:用什么演员的工作对我来说

相反,我使用了类似的期货的方式pagoda_5b在他的回答解释说:

移动状态栏和进度条出def top,使他们的全局变量:

object MyGUIProject extends SimpleSwingApplication { 

    val txtStatus = new Label //Global 
    val progressBar = new ProgressBar //Global 

    def top = new MainFrame { 
    ... 
    } 
} 

通话时,我CL未来的后台进程ICK按钮:

def updateStatus(txt: String) = { 
    val fut = future { 
     Swing.onEDT { 
     txtStatus.text = txt 
     } 
    } 
} 

def doLongBackgroundProcess(str:String) = { 
    val taskList = getTasks(str) 
    taskList foreach {x=> 
     updateStatus("Currently doing task: " + x) 
     //Start doing it 
    } 
} 
+0

要从用户界面启动Scala程序,请确保您的程序不在EDT上运行。 'SwingWorker'可以减轻这个问题的工作量,或者你可以使用'Executors'。查看“Swing中的并发”:http://stackoverflow.com/tags/swing/info – 2013-03-04 14:28:19

+0

高度相关http://stackoverflow.com/questions/13516752/using-opengl-with-akka-actors-guaranteeing-a-单线程用于指定 – 2013-03-04 14:29:18

+0

请参见[如何使用SwingWorker工作](http://stackoverflow.com/a/11546203/597657)。 – 2013-03-04 15:07:00

回答

7

斯卡拉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 
    } 
} 

这是最简单的例子:

  1. 完成后,没有进步
  2. 我们只处理成功的案例

我们只更新

为了处理(1)你可以结合不同的期货,使用方法map/flatMap odb的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 
    } 
} 

为了应对(2)你可以注册一个回调onFailure或处理这两种情况做与onComplete

对于相关例子:scaladocs和相关SIP(虽然SIP的例子似乎已经过时,他们应该给你一个好主意)

+0

感谢您的详细代码!当我回到家时,我会试试它,并让你知道它是怎么回事 – 2013-03-05 07:43:39

+0

请做,并让我们知道,如果你发现任何问题或问题 – 2013-03-05 13:48:59

+0

对不起,延迟回复。期货对我来说是完美的,我不知道他们甚至存在!花了一些时间弄清楚,但最终对我有效,感谢你的答案。我做了非常类似的事情,将来将其称为后台进程,将所需的UI组件移到全局变量中,并使用Future通过后台进程使用Swing.EDT – 2013-03-10 05:53:48

1

如果你需要的东西简单,你可以在一个新的线程中运行的长期的任务就是确定:

val btnStart = new Button { 
     text = "Click me to start" 
     reactions += { 
     case ButtonClicked(_) => { 
      val myFuture = future { 
      doLongBackgroundProcess(someString) 
      } 

      myFuture onSuccess { 
       case _ => Swing.onEDT { 
        txtStatus.text = "Done" 
       } 
      } 
     } 
    } 
} 

使用futureSwing.onEDT更新他们的后台进程更新它在美国东部时间:

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)) 
    }) 
+0

有趣的方法。我只想着演员,我完全忘记了使用简单的线程 – 2013-03-05 07:42:42

+0

除非你真的需要演员,否则我会遵循KISS – 2013-03-05 14:49:26

+0

我认为在'thread'函数中应该是'start()'而不是'run()'(now它似乎阻止了使用线程的目的)。 – monnef 2015-01-31 13:11:31

4

如果你想使用演员,下面可能会为你工作。

有两个参与者:

  • WorkerActor这确实数据处理(这里,有简单的循环用的Thread.sleep)。该角色将消息发送有关工作进展到另一个演员:
  • GUIUpdateActor - 通过调用handleGuiProgressEvent方法

UI更新方法handleGuiProgressEvent接收更新事件接收有关进度和更新UI更新。 重要的一点是,此方法由Actor使用Akka线程之一调用,并使用Swing.onEDT在Swing事件分派线程中执行Swing工作。

您可以将以下内容添加到各个地方以查看当前线索是什么。

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

代码是可运行的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 
    } 
    } 

} 
+2

来更新它们。非常感谢。由于缺少(或过多)文档,并且还因为API的版本不同,所以对于新手来说,要为akka和演员做任何事情都很困难。现在,我相信期货完全按照我想要的方式。我现在觉得演员对UI有点矫枉过正 – 2013-03-10 05:51:13

+0

不错,谢谢。仅供参考,我必须分别使用'Props(classOf [GuiUpdateActor],this)'和'Props(classOf [WorkerActor],guiUpdateActor)'作为每个'system.actorOf()'调用的第一个参数(akka版本2.3-M2)。 – 2013-12-30 19:07:55