(Quick Reference)

9 非同期プログラミング - Reference Documentation

Authors: Graeme Rocher, Peter Ledbrook, Marc Palmer, Jeff Brown, Luke Daley, Burt Beckwith

Version: 2.3.0

Translated by: T.Yamamoto, Japanese Grails Doc Translating Team. Special thanks to NTT Software.
【注意】このドキュメントの内容はスナップショットバージョンを元に*意訳*されているため、一部現行バージョンでは未対応の機能もあります。

9 非同期プログラミング

With modern hardware featuring multiple cores, many programming languages have been adding asynchronous, parallel programming APIs, Groovy being no exception.
近年マルチコアを搭載したハードウェアの登場により、さまざまなプログラミング言語で非同期処理、並列プログラミングのAPIが追加されてきました。Groovyもまた例外ではありません。

The excellent GPars project features a whole range of different APIs for asynchronous programming techniques including actors, promises, STM and data flow concurrency.
GParsではActor、Promise、STM、そしてDataflow Concurrencyといった、非同期プログラミングで必要となるさまざまなAPIを提供しています。

Added Grails 2.3, the Async features of Grails aim to simplify concurrent programming within the framework and include the concept of Promises and a unified event model.
Grails 2.3では、Promiseの概念と統一されたイベントモデルを取り入れることで、フレームワーク内での並行プログラミングを簡単にする非同期機能を追加しました。

9.1 Promises

A Promise is a concept being embraced by many concurrency frameworks. They are similar to java.util.concurrent.Future instances, but include a more user friendly exception handling model, useful features like chaining and the ability to attach listeners.
Promiseはさまざまな並行フレームワークで取り入れられている概念です。この概念はjava.util.concurrent.Futureによく似ていますが、より使いやすい例外ハンドリングモデルや、非同期処理の連鎖、リスナーの追加といったさまざまな便利な機能を備えています。

Promise Basics

Promiseの基本

In Grails the grails.async.Promises class provides the entry point to the Promise API:
GrailsではPromise APIの入り口としてgrails.async.Promisesクラスを提供しています。

import static grails.async.Promises.*

To create promises you can use the task method, which returns an instance of the grails.async.Promise interface:
promiseを作成するにはtaskメソッドを使用します。これはgrails.async.Promiseインタフェースのインタンスを返します。

def p1 = task { 2 * 2 }
def p2 = task { 4 * 4 }
def p3 = task { 8 * 8 }
assert [4,16,64] == waitAll(p1, p2, p3)

The waitAll method waits synchronously, blocking the current thread, for all of the concurrent tasks to complete and returns the results.
waitAllメソッドは現在のスレッドをブロックした状態で処理を待ち合わせ、すべての並行タスクが完了した時点で結果を返します。

If you prefer not to block the current thread you can use the onComplete method:
もし現在のスレッドをブロックしたくない場合はonCompleteメソッドを使用します。

onComplete([p1,p2,p3]) { List results ->
   assert [4,16,64] == results
}

The waitAll method will throw an exception if an error occurs executing one of the promises. The originating exception will be thrown. The onComplete method, however, will simply not execute the passed closure if an exception occurs. You can register an onError listener if you wish to handle exceptions without blocking:
waitAllメソッドはpromiseのいずれかでエラーが発生した場合に例外をスローします。これは発信元の例外がスローされます。onCompleteメソッドを使用した場合は、単にクロージャが実行されないだけです。もしブロッキングせずに例外を処理したい場合はonErrorメソッドを使用してリスナーを登録できます。

onError([p1,p2,p3]) { Throwable t ->
   println "An error occured ${t.message}"
}

If you have just a single long running promise then the grails.async.Promise interface provides a similar API on the promise itself. For example:
もし単一で処理に長い時間が必要となるpromiseがある場合は、grails.async.Promiseインターフェース自身で同様のAPIを提供しています。例えば以下のように使用します。

import static java.util.concurrent.TimeUnit.*
import static grails.async.Promises.*

Promise p = task { // Long running task } p.onError { Throwable err -> println "An error occured ${err.message}" } p.onComplete { result -> println "Promise returned $result" } // block until result is called def result = p.get() // block for the specified time def result = p.get(1,MINUTES)

Promise Chaining

Promiseのチェーン

It is possible to chain several promises and wait for the chain to complete using the then method:
thenメソッドを使用してpromiseをチェーンすることで、チェーンの完了を待ち合わせることができます。

final polish = { … }
final transform = { … }
final save = { … }
final notify = { … }

Promise promise = task { // long running task } promise.then polish then transform then save then { // notify end result }

If an exception occurs at any point in the chain it will be propagated back to the caller and the next step in the chain will not be called.
もし途中のチェーンで例外が発生した場合は、呼び出し元に例外がスローされ、次のチェーンが呼ばれることはありません。

Promise Lists and Maps

Promiseのリストとマップ

Grails' async API also features the concept of a promise lists and maps. These are represented by the grails.async.PromiseList and grails.async.PromiseMap classes respectively.
Grailsの非同期APIはpromiseのリストとマップの概念を備えています。これらはぞれぞれgrails.async.PromiseListgrails.async.PromiseMapのクラスとして実装されています。

The easiest way to create a promise list or map is via the tasks method of the Promises class:
promiseのリストやマップを作成する簡単な方法は、Promisesクラスのtasksメソッドを使用することです。

import static grails.async.Promises.*

def promiseList = tasks { 2 * 2 }, { 4 * 4}, { 8 * 8 }

assert [4,16,64] == promiseList.get()

The tasks method, when passed a list of closures, returns a PromiseList. You can also construct a PromiseList manually:
tasksメソッドはクロージャのリストを引数に取りPromiseListを返します。または手動でPromiseListを構築することもできます。

import grails.async.*

def list = new PromiseList() list << { 2 * 2 } list << { 4 * 4 } list << { 8 * 8 } list.onComplete { List results -> assert [4,16,64] == results }

The PromiseList class does not implement the java.util.List interface, but instead returns a java.util.List from the get() method
PromiseListクラスはjava.util.Listのインタフェースを実装していません。代わりにget()メソッドを使用することでjava.util.Listとして取得できます。

Working with PromiseMap instances is largely similar. Again you can either use the tasks method:
PromiseMapを扱う場合もほとんど同じです。tasksメソッドを使用する場合は以下のようにします。

import static grails.async.Promises.*

def promiseList = tasks one:{ 2 * 2 }, two:{ 4 * 4}, three:{ 8 * 8 }

assert [one:4,two:16,three:64] == promiseList.get()

Or construct a PromiseMap manually:
手動でPromiseMapを使用して構築する場合は以下のようにします。

import grails.async.*

def list = new PromiseMap() map['one'] = { 2 * 2 } map['two'] = { 4 * 4 } map['three'] = { 8 * 8 } map.onComplete { Map results -> assert [one:4,two:16,three:64] == results }

Promise Factories

Promiseファクトリ

The Promises class uses a grails.async.PromiseFactory instance to create Promise instances.
PromisesクラスはPromiseインスタンスを生成するためにgrails.async.PromiseFactoryインスタンスを使用しています。

The default implementation uses the GPars concurrency library and is called org.grails.async.factory.gpars.GparsPromiseFactory, however it is possible to swap implementations by setting the Promises.promiseFactory variable.
デフォルトの実装ではGParsの並行ライブリであるorg.grails.async.factory.gpars.GparsPromiseFactoryが使用されていますが、Promises.promiseFactoryの値を変更することで実装を差し替えることができます。

One common use case for this is unit testing, typically you do not want promises to execute asynchronously during unit tests, as this makes tests harder to write. For this purpose Grails ships with a org.grails.async.factory.SynchronousPromiseFactory instance that makes it easier to test promises:
よくある使用例はユニットテストです。たいていの場合にpromiseを非同期に実行して、ユニットテストを行うことは非常に困難になります。Grailsでは、このような目的のためにorg.grails.async.factory.SynchronousPromiseFactoryを提供しており、これを使用することで簡単にpromiseのテストが行えます。

import org.grails.async.factory.*
import grails.async.*

Promises.promiseFactory = new SynchronousPromiseFactory()

Using the PromiseFactory mechanism is theoritically possible to plug in other concurrency libraries into the Grails framework.
また、PromiseFactoryの仕組みを使用することで、Grailsに他の並行ライブラリを組み込みということも論理的には可能です。

DelegateAsync Transformation

DelegateAsync変換

It is quite common to require both synchronous and asynchronous versions of the same API. Developing both can result in a maintenance problem as typically the asynchronous API would simply delegate to the synchronous version.
同じAPIにおいて、同期API/非同期APIの両方が必要となることがよくあります。このような場合に、非同期APIは単に同期APIに処理を委譲させるといった方法では、保守上問題を引き起こすことがあります。

The DelegateAsync transformation is designed to mitigate this problem by transforming any synchronous API into an asynchronous one.
DelegateAsync変換は、同期APIを非同期APIに変換することで、この問題を軽減できるように設計しています。

For example, consider the following service:
例えば次のようなサービスについて考えてみます。

class BookService {	
    List<Book> findBooks(String title) {
      // implementation
    }
}

The findBooks method executes synchronously in the same thread as the caller. To make an asynchronous version of this API you can define another class as follows:
findBooksメソッドは呼び出し元と同じスレッドで同期的に実行されます。ここで次のような別クラスを定義することで、このAPIの非同期バージョンを作成できます。

import grails.async.*

class AsyncBookService { @DelegateAsync BookService bookService }

The DelegateAsync transformation will automatically add a new method that looks like the following to the AsyncBookService class:
DelegateAsync変換は、自動的にAsyncBookServiceクラスに次のようなメソッドを追加します。

Promise<List<Book>> findBooks(String title) {
    Promises.task {
       bookService.findBooks(title)
    }
}

As you see the transform adds equivalent methods that return a Promise and execute asynchronously.
上記のようにDelegateAsync変換は非同期に処理を行い、Promiseを返すメソッドを追加します。

The AsyncBookService can then be injected into other controllers and services and used as follows:
このAsyncBookServiceは、他のコントローラやサービスに注入され、次のように使用できます。

AsyncBookService asyncBookService
def findBooks(String title) {
    asyncBookService.findBooks(title)
       .onComplete { List results ->
          println "Books = ${results}"				
       }
}

9.2 非同期GORM

Since Grails 2.3, GORM features an asynchronous programming model that works across all supported datastores (Hibernate, MongoDB etc.).
Grails 2.3から、GORMでサポートされているすべてのデータストア(Hibernate、MongoDBなど)に対して、横断的に動作する非同期プログラミングモデルを提供しています。

Async Namespace

Asyncネームスペース

The Asynchronous GORM API is available on every domain class via the async namespace.
非同期GORM APIは、すべてのドメインクラスに対しasyncネームスペースを通じて提供されます。

For example, the following code listing reads 3 objects from the database asynchronously:
例えば次のコードはデータベースから非同期に3つのオブジェクトを読み込みます。

import static grails.async.Promises.*

def p1 = Person.async.get(1L) def p2 = Person.async.get(2L) def p3 = Person.async.get(3L) def results = waitAll(p1, p2, p3)

Using the async namespace, all the regular GORM methods are available (even dynamic finders), but instead of executing synchronously, the query is run in the background and a Promise instance is returned.
asyncネームスペースでは、通常使用可能なすべてのGORMメソッドが使用できます(ダイナミックファインダーのような)。ただし同期的に実行されるのではなく、バックグラウンドでクエリーが実行され、Promiseインスタンスを返します。

The following code listing shows a few common examples of GORM queries executed asynchronously:
次のコードは、GORMのクエリーを非同期に実行します。

import static grails.async.Promises.*

Person.async.list().onComplete { List results -> println "Got people = ${results}" } def p = Person.async.getAll(1L, 2L, 3L) List results = p.get()

def p1 = Person.async.findByFirstName("Homer") def p2 = Person.async.findByFirstName("Bart") def p3 = Person.async.findByFirstName("Barney") results = waitAll(p1, p2, p3)

Async and the Session

非同期とセッション

When using GORM async each promise is executed in a different thread. Since the Hibernate session is not concurrency safe, a new session is bound per thread.
GORMを非同期に使用する場合、promiseはそれぞれ別々のスレッドで実行されます。またHibernateのセッションはスレッドセーフではないため、スレッドごとに新しいセッションをバインドします。

This is an important consideration when using GORM async (particularly with Hibernate as the persistence engine). The objects returned from asynchronous queries will be detached entities.
非同期にGORMを使用する場合は、この点に十分考慮する必要があります(特に永続化エンジンにHibernateを使用する場合)。特に非同期クエリから返されるオブジェクトは、デタッチされたエンティティであることに注意をしてください。

This means you cannot save objects returned from asynchronous queries without first merging them back into session. For example the following will not work:
これは非同期クエリから返されたオブジェクトは、初めに起動したセッションにはマージされず、保存できないことを意味しています。例えば次のようなコードは正しく動作しません。

def promise = Person.async.findByFirstName("Homer")
def person = promise.get()
person.firstName = "Bart"
person.save()

Instead you need to merge the object with the session bound to the calling thread. The above code needs to be written as:
正しく動作させるには、呼び出し元のスレッドにバインドされているセッションにオブジェクトをマージする必要があります。

def promise = Person.async.findByFirstName("Homer")
def person = promise.get()
person.merge()
person.firstName = "Bart"

Note that merge() is called first because it may refresh the object from the cache or database, which would result in the change being lost. In general it is not recommended to read and write objects in different threads and you should avoid this technique unless absolutely necessary.
まず始めにmerge()を呼びだしています。これはキャッシュまたはデータベースのデータが再読み込みされ、それまでに行った変更が失われてしまうためです。また一般的に異なるスレッド間でオブジェクトを読み書きすることは推奨されません。もし必要がない限り、この方法は絶対に避けるべきです。

Finally, another issue with detached objects is that association lazy loading will not work and you will encounter LazyInitializationException errors if you do so. If you plan to access the associated objects of those returned from asynchronous queries you should use eager queries (which is recommended anyway to avoid N+1 problems).
最後に、デタッチされたオブジェクトの別の問題として、関連をlazyローディングしようとするとLazyInitializationExceptionを引き起こすという問題があります。もし非同期クエリから取得したオブジェクトの関連にアクセスする場合は、eagerクエリとして実行してください(N+1問題を避けるために、強くお勧めします)。

Multiple Asynchronous GORM calls

複数の非同期GORMの呼び出し

As discussed in the previous section you should avoid reading and writing objects in different threads as merging tends to be inefficient.
上記で説明したように、マージは問題を引き起こす可能性が高いため、異なるスレッド間でのオブジェクトの読み書きは避けるべきです。

However, if you wish to do more complex GORM work asynchronously then the GORM async namespace provides a task method that makes this possible. For example:
しかし、GORMのasyncネームスペースが提供しているtaskメソッドを使用することで、より複雑なGORMの非同期処理が可能になります。例えば以下のように使用します。

def promise = Person.async.task {
    withTransaction {
       def person = findByFirstName("Homer")
       person.firstName = "Bart"
       person.save(flush:true)    
    }
}

Person updatedPerson = promise.get()

Note that the GORM task method differs from the static Promises.task method in that it deals with binding a new session to the asynchronous thread for you. If you do not use the GORM version and do asynchronous work with GORM then you need to do this manually. Example:
GORMのtaskメソッドは静的なPromises.taskメソッドとは異なり、非同期処理のスレッドに対して自動的に新いセッションをバインドします。もしGORMのtaskメソッドを使用せず非同期にGORMの処理を行う場合は、これを手動で行う必要があります。

import static grails.async.Promises.*

def promise = task { Person.withNewSession { // your logic here } }

Async DetachedCriteria

非同期DetachedCriteria

The DetachedCriteria class also supports the async namespace. For example you can do the following:
DetachedCriteriaクラスもasyncネームスペースをサポートしています。例えば次のように使用できます。

DetachedCriteria query = Person.where {
    lastName == "Simpson"
}

def promise = query.async.list()

9.3 非同期リクエストハンドリング

If you are deploying to a Servlet 3.0 container such as Tomcat 7 and above then it is possible to deal with responses asynchronously.
もしアプリケーションをTomcat 7のようなServlet 3.0対応のコンテナにデプロイしている場合は、非同期にレスポンスを返すことが可能です。

In general for controller actions that execute quickly there is little benefit in handling requests asynchronously. However, for long running controller actions it is extremely beneficial.
一般的にコントローラのアクションがすぐに完了するような場合は、非同期にリクエストを処理してもほとんど効果はありません。しかし、アクションに長い時間がかかる場合は、非常に効果があります。

The reason being that with an asynchronous / non-blocking response, the one thread == one request == one response relationship is broken. The container can keep a client response open and active, and at the same time return the thread back to the container to deal with another request, improving scalability.
非同期/ノンブロッキングのレスポンスでは、1スレッド == 1リクエスト == 1レンスポンスの関係が成りたたなくなります。これはクライアントとの接続を保ちつつ、他のリクエストを処理するためにスレッドをコンテナに返すことで、スケーラビリティを向上させるためです。

For example, if you have 70 available container threads and an action takes a minute to complete, if the actions are not executed in a non-blocking fashion the likelihood of all 70 threads being occupied and the container not being able to respond is quite high and you should consider asynchronous request processing.
例えば、もしコンテナが70のスレッドを使用可能で、アクションを処理するのに1分かかるとします。もしこのアクションをノンブロッキングで実行しない場合は、70のスレッドすべてを占有し、レスポンスを返せない状態になる可能性が非常に高くなります。このような場合は、非同期にリクエストを処理することを考える必要があります。

Since Grails 2.3, Grails features a simplified API for creating asynchronous responses built on the Promise mechism discussed previously.
Grails 2.3からは、これまでに説明してきたPromiseの仕組みを使い、簡単に非同期レスポンスが作成できるAPIを提供しています。

The implementation is based on Servlet 3.0 async so to enable the async features you need to set your servlet target version to 3.0 in BuildConfig.groovy:
実装はServlet 3.0の非同期処理をベースにしており、この非同期機能を有効にするにはBuildConfig.groovyでサーブレットの対象バージョンを3.0に設定する必要があります。

grails.servlet.version = "3.0"

Async Models

非同期モデル

A typical activity in a Grails controller is to produce a model (a map of key/value pairs) that can be rendered by a view.
Grailsのコントローラにおける役割の一つは、ビューに表示するモデル(キーと値のマップ)を生成することです。

If the model takes a while to produce then the server could arrive at a blocking state, impacting scalability. You tell Grails to build the model asynchronously by returning a grails.async.PromiseMap via the Promises.tasks method:
もしこのモデルの生成に時間がかかる場合は、サーバが他のリクエスト処理をブロックする状態となり、スケーラビリティに影響を与えます。このような場合は、Promises.tasksメソッドを使用しgrails.async.PromiseMapを返すことで非同期にモデルを生成できます。

import static grails.async.Promises.*
…
def index() {
   tasks books: Book.async.list(),
         totalBooks: Book.async.count(),
         otherValue: {
           // do hard work
         }
}

Grails will handle the response asynchronously, waiting for the promises to complete before rendering the view. The equivalent synchronous action of the above is:
Grailsはビューをレンダリングする前にpromiseの処理を待ち合わせることで、非同期にレスポンスを処理します。もし、これを同期的に処理した場合は以下のようになります。

def index() {
    def otherValue = …
	[ books: Book.list() , 
	  totalBooks: Book.count(),
	  otherValue: otherValue  ]
}

You can even render different view by passing the PromiseMap to the model attribute of the render method:
また異なるビューを使用してリンダリングを行いたい場合は、renderメソッドのmodel属性にPromiseMapを設定します。

import static grails.async.Promises.*
…
def index() {
   render view:"myView", model: tasks( one:{ 2 * 2 },
                                       two:{ 3 * 3 } )
}

Async Response Rendering

非同期レスポンスのレンダリング

You can also write to the response asynchronously using promises in Grails 2.3 and above:
Grails 2.3ではpromiseを使用して、非同期にレスポンスを書くこともできます。

import static grails.async.Promises.*
class StockController {

def stock(String ticker) { task { ticker = ticker ?: 'GOOG' def url = new URL("http://download.finance.yahoo.com/d/quotes.csv?s=${ticker}&f=nsl1op&e=.csv") Double price = url.text.split(',')[-1] as Double render "ticker: $ticker, price: $price" } } }

The above example using Yahoo Finance to query stock prices, executing asynchronously and only rendering the response once the result has been obtained. This is done by returning a Promise instance from the controller action.
上記の例では、株価を参照するためにYahoo Financeを使用して、非同期に処理を実行しつつ結果が得られた時点でレスポンスをレンダリングしています。このように動作させるには、コントローラのアクションからPromiseインスタンスを返します。

If the Yahoo URL is unresponsive the original request thread will not be blocked and the container will not become unresponsive.
このようにすることで、YahooのURLがレスポンスを返さない場合にも、元のスレッドがブロッキングされず、またコンテナが応答しないということもなく処理できます。

9.4 Servlet 3.0 Async

In addition to the higher level async features discussed earlier in the section, you can access the raw Servlet 3.0 asynchronous API from a Grails application.
これまでに説明してきた、より高レベルな非同期機能に加えて、GrailsのアプリケーションからServlet 3.0の非同期APIに直接アクセスすることもできます。

Servlet 3.0 Asynchronous Rendering

Servlet 3.0の非同期レンダリング

You can render content (templates, binary data etc.) in an asynchronous manner by calling the startAsync method which returns an instance of the Servlet 3.0 AsyncContext. Once you have a reference to the AsyncContext you can use Grails' regular render method to render content:
非同期にコンテンツ(テンプレートやバイナリなど)のレンダリングには、Servlet 3.0のAsyncContextインスタンスを返す、startAsyncを使用します。このメソッドを使用してAsyncContextへの参照を取得したら、あとは通常のrenderメソッドを使用してコンテンツをレンダリングするだけです。

def index() {
    def ctx = startAsync()
    ctx.start {
        new Book(title:"The Stand").save()
        render template:"books", model:[books:Book.list()]
        ctx.complete()
    }
}

Note that you must call the complete() method to terminate the connection.
接続を終了するには、completeメソッドを呼び出す必要があることに注意してください。

Resuming an Async Request

非同期リクエストの再開

You resume processing of an async request (for example to delegate to view rendering) by using the dispatch method of the AsyncContext class:
非同期リクエストの処理(例えばビューをレンダリングするために処理を委譲する)を再開するには、AsyncContextクラスのdispatchメソッドを使用します。

def index() {
    def ctx = startAsync()
    ctx.start {
        // do working
        …
        // render view
        ctx.dispatch()
    }
}