6

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

由于 Scala 演员已被弃用,我很难尝试通过 Akka 来运行某种基本的多线程。Akka 网站上给出的例子也相当复杂。

但更重要的是,我发现很难思考如何尝试这个问题。我能想到的是:

  1. 后台程序作为一个参与者运行
  2. UI是主程序
  3. 让另一个演员告诉 UI 更新一些东西

第 3 步让我感到困惑。如何在不锁定某个变量的情况下告诉 UI?

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

4

4 回答 4

7

对于 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
    }
}

这是最简单的情况:

  1. 我们只在完成后更新,没有任何进展
  2. 我们只处理成功的案例

为了处理(1),您可以使用实例上的map/flatMap方法组合不同的期货。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 示例似乎已经过时,但它们应该给你一个好主意)

于 2013-03-04T16:43:27.183 回答
5

如果您想使用 Actors,以下可能对您有用。

有两个演员:

  • 进行数据处理的WorkerActor(这里有一个带有 Thread.sleep 的简单循环)。这个actor向另一个actor发送关于工作进度的消息:
  • GUIUpdateActor - 通过调用handleGuiProgressEvent方法接收有关进度和更新 UI 的更新

UI 更新方法handleGuiProgressEvent接收更新事件。重要的一点是,此方法由 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
    }
  }

}
于 2013-03-05T08:02:04.097 回答
0

如果你需要一些简单的东西,你可以在一个新线程中运行长任务,并确保在 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))
  })
于 2013-03-04T15:01:09.620 回答
0

你可以定义你自己ExecutionContext的在 Swing Event Dispatch Thread 上执行任何事情SwingUtilities.invokeLater,然后使用这个上下文来调度需要由 Swing 执行的代码,仍然保留链接FutureScala 方式的能力,包括在它们之间传递结果。

  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)
于 2019-02-27T10:45:29.717 回答