Futureで非同期処理をする方法

Scala 3 (Dotty 0.26.0-RC1) 2.13.3 2.12.12
最終更新:2020年5月19日

[AD] scalapediaでは記事作成ボランティアを募集しています

この記事ではFutureクラスを使って非同期処理をする方法を解説します。

事前準備をする

まずは以下のListBufferについて考えてみます。

val buffer: ListBuffer[String] = ListBuffer()

このbufferに対して、処理の進捗状況を文字列で書き込んでいくプログラムを想定します。

ふたつのメソッドを用意して、それぞれ同時に実行して並行に処理してみましょう。

2つのメソッドを準備する

まずは1つ目のメソッド。

def process1(): Unit = { buffer += "start first process" Thread.sleep(100) buffer += "finish first process" }

開始時と終了時にそれぞれ文字列をbufferに対して挿入しています。

これはすこし時間がかかる処理であるのを模して、Thread.sleepを挟んでいます。

そして2つ目のメソッド。

def process2(): Unit = { buffer += "start second process" buffer += "finish second process" }

こちらは即座に処理が終了するのを模して、開始直後に終了時の文字列をbufferに対して挿入します。

process1process2も同期的に実行するメソッドです。

実行コンテキスト(ExecutionContext)を準備する

Futureで非同期処理を行うには、実行コンテキスト(以下、ExecutionContext)を具体的に用意する必要があります。

Scalaにおける実行コンテキストとは、特定の処理がどのように実行されるべきであるのかについてのきまりです。

ExecutionContextは、その実行コンテキストについて、実際にどのように実行されるかを定めたクラスです。

Futureを使用して処理する際には、どのようなExecutionContextにて実行するべきかを示してあげましょう。

ここでは、Scalaが標準で用意しているExecutionContextを使用することにします。

Scalaのプログラムは、特に非同期処理をしていない限り、標準のExecutionContextにおいて実行されています。

標準のExecutionContextでは、JVMが使用できるプロセッサ数まで並行に処理することができます。

つまり、もともとの処理に加えて、プロセッサ数 - 1個の処理まで並行に処理することができるわけですね。

標準のExecutionContextを使用するには、以下のようにします。

import scala.concurrent.ExecutionContext.Implicits.global

Futureに対してExecutionContextを暗黙的に渡したい場合には、後者のようにします。

Futureの各メソッドは暗黙のパラメータとしてExecutionContextをとります。

したがって後者のようにすると毎回ExecutionContextを渡す手間が省けて、コードを簡略化することができます。

また、Futureを使用する都度明示的にExecutionContextを渡したい場合には、前者のようにすればOKです。

import scala.concurrent.ExecutionContext.global

非同期処理を実行する

では実際に非同期処理を実行してみましょう。

先述のメソッド、process1process2を同時に実行するとどうなるかを見てみましょう。

以下のようにメソッドをFuture( )で囲むと、非同期処理として実行することができます。

val f1 = Future(process1()) val f2 = Future(process2())

process1は非同期処理として即座に実行され、直後にprocess2も実行されます。

したがって、実質的に同時に実行したことになります。

process1を先に開始しましたが、process1は時間がかかるため、process2の方が先に終わると期待されます。

それでは、bufferの中身を見てみましょう。

以下のようにするとf1f2が完了した状態のbufferを返すことができます。

for { _ <- f1 _ <- f2 } yield buffer

bufferの中身は以下のようになっています。

ListBuffer(start first process, start second process, finish second process, finish first process)

実際にprocess1の開始、process2の開始、process2の完了、process1の完了という順番になっていることがわかりますね。

サイト内検索


カテゴリ「並行並列処理」の記事


カテゴリ「並行並列処理」の記事