6

UI(Swingでも)を介してトリガーしたいScalaプログラムを作成しました。問題は、トリガーすると、バックグラウンド プログラムが完了するまで UI がハングすることです。これを回避する唯一の方法は、プログラムを別のスレッド/アクターで実行し、必要に応じて UI を更新することであると考えるようになりました。更新には、現在処理中のファイルを示すステータス バーと進行状況バーが含まれます。

Scala アクターは非推奨になっているため、Akka を調べてある種の基本的なマルチスレッドを実行するのに苦労しています。Akka の Web サイトにある例も非常に複雑です。

しかし、それ以上に、この問題をどのように試みるかについて頭を悩ますのが難しいと感じています。私が思いつくことができるのは:

  1. バックグラウンド プログラムは 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メソッドを使用して、さまざまな先物を組み合わせることができます。それらが呼び出されると、UI で進行状況を更新できます (常にブロックFutureで行うようにしてください)。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

アクターを使用する場合は、次の方法が役立つ場合があります。

次の 2 つのアクターがあります。

  • データ処理を行うWorkerActor (ここでは、Thread.sleep による単純なループがあります)。このアクターは、作業の進行状況に関するメッセージを別のアクターに送信します。
  • GUIUpdateActor - handleGuiProgressEventメソッドを呼び出して、進行状況に関する更新を受け取り、UI を更新します。

UI 更新メソッドhandleGuiProgressEventは更新イベントを受け取ります。重要な点は、このメソッドが Akka スレッドの 1 つを使用して Actor によって呼び出され、Swing イベント ディスパッチ スレッドで Swing 作業を行うために Swing.onEDT を使用することです。

以下をさまざまな場所に追加して、現在のスレッドを確認できます。

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 によって実行される必要があるコードをスケジュールし、Scala の方法で連鎖する機能を保持し、Futureそれらの間で結果を渡すことができます。

  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 に答える