web-dev-qa-db-ja.com

KotlinコルーチンでThreadLocalに依存するコードを使用する方法

SLF4j MDC 、トランザクションマネージャ、セキュリティマネージャなど、一部のJVMフレームワークはThreadLocalを使用してアプリケーションの呼び出しコンテキストを格納します。

ただし、Kotlinコルーチンは異なるスレッドでディスパッチされるため、どのように機能させることができますか?

(質問は GitHubの問題 に触発されています)

22
Roman Elizarov

コルーチンのThreadLocalへの類似は CoroutineContext です。

ThreadLocal- usingライブラリと相互運用するには、フレームワーク固有のスレッドローカルをサポートするカスタム ContinuationInterceptor を実装する必要があります。

例を示します。特定のThreadLocalに依存するいくつかのフレームワークを使用して、アプリケーション固有のデータ(この例ではMyData)を格納するとします。

val myThreadLocal = ThreadLocal<MyData>()

コルーチンで使用するには、MyDataの現在の値を保持し、コルーチンがスレッドで再開されるたびに対応するThreadLocalに挿入するコンテキストを実装する必要があります。コードは次のようになります。

class MyContext(
    private var myData: MyData,
    private val dispatcher: ContinuationInterceptor
) : AbstractCoroutineContextElement(ContinuationInterceptor), ContinuationInterceptor {
    override fun <T> interceptContinuation(continuation: Continuation<T>): Continuation<T> =
        dispatcher.interceptContinuation(Wrapper(continuation))

    inner class Wrapper<T>(private val continuation: Continuation<T>): Continuation<T> {
        private inline fun wrap(block: () -> Unit) {
            try {
                myThreadLocal.set(myData)
                block()
            } finally {
                myData = myThreadLocal.get()
            }
        }

        override val context: CoroutineContext get() = continuation.context
        override fun resume(value: T) = wrap { continuation.resume(value) }
        override fun resumeWithException(exception: Throwable) = wrap { continuation.resumeWithException(exception) }
    }
}

コルーチンで使用するには、使用するディスパッチャーをMyContextでラップし、データの初期値を指定します。この値は、コルーチンが再開されるスレッドのスレッドローカルに配置されます。

launch(MyContext(MyData(), CommonPool)) {
    // do something...
}

上記の実装は、実行されたスレッドローカルへの変更を追跡し、このコンテキストに保存するため、この方法で、複数の呼び出しがコンテキストを介して「スレッドローカル」データを共有できます。

[〜#〜]更新[〜#〜]kotlinx.corutinesバージョン0.25.0以降、Java =コルーチンコンテキストエレメントとしてのThreadLocalインスタンス。詳細は このドキュメント を参照してください。また、kotlinx-coroutines-slf4j統合モジュールを介したSLF4J MDCのサポートがすぐに利用できます。

29
Roman Elizarov