调试 scala futures - 如何确定未来的执行上下文
debugging scala futures - how to determine the future's execution context
与我发布的另一个问题相关 ()
在调试未来时,调用堆栈的信息量不是很大(因为调用上下文通常在另一个线程和另一个时间)。
当可能有不同的路径通向相同的未来代码时(例如从代码中的许多地方调用 DAO 的使用等),这尤其成问题。
你知道一个优雅的解决方案吗?
我正在考虑传递一个 token/request ID(对于由 Web 服务器请求启动的流)——但这需要传递它——并且不会包含任何你可以在堆栈跟踪中看到的状态。
也许传递一个堆栈? :)
假设你做了一个 class
case class Context(requestId: Int, /* other things you need to pass around */)
有两种隐式发送它的基本方法:
1) 向需要它的任何函数添加隐式 Context
参数:
def processInAnotherThread(/* explicit arguments */)(
implicit evaluationContext: scala.concurrent.EvaluationContext,
context: Context): Future[Result] = ???
def processRequest = {
/* ... */
implicit val context: Context = Context(getRequestId, /* ... */)
processInAnotherThread(/* explicit parameters */)
}
缺点是每个需要访问 Context
的函数都必须有这个参数,它会在函数签名中乱扔垃圾。
2) 放入DynamicVariable
:
// Context companion object
object Context {
val context: DynamicVariable[Context] =
new DynamicVariable[Context](Context(0, /* ... */))
}
def processInAnotherThread(/* explicit arguments */)(
implicit evaluationContext: scala.concurrent.EvaluationContext
): Future[Result] = {
// get requestId from context
Context.context.value.requestId
/* ... */
}
def processRequest = {
/* ... */
Context.context.withValue(Context(getRequestId, /* ... */)) {
processInAnotherThread(/* explicit parameters */)
}
}
缺点是
- 在处理的深处并不能立即清楚是否有一些上下文可用,它有什么内容,而且参照透明性也被破坏了。我认为最好严格限制可用
DynamicVariable
的数量,最好不要超过 1 个或最多 2 个并记录它们的使用。
- context 的所有内容必须具有默认值或
null
s,或者默认情况下它本身必须是 null
(new DynamicVariable[Context](null)
)。在处理之前忘记初始化 Context
或其内容可能会导致严重错误。
DynamicVariable
仍然比某些全局变量好得多,并且不会影响不以任何方式直接使用它的函数的签名。
在这两种情况下,您都可以使用 case class
的 copy
方法更新现有 Context
的内容。例如:
def deepInProcessing(/* ... */): Future[Result] =
Context.context.withValue(
Context.context.value.copy(someParameter = newParameterValue)
) {
processFurther(/* ... */)
}
与我发布的另一个问题相关 (
假设你做了一个 class
case class Context(requestId: Int, /* other things you need to pass around */)
有两种隐式发送它的基本方法:
1) 向需要它的任何函数添加隐式 Context
参数:
def processInAnotherThread(/* explicit arguments */)(
implicit evaluationContext: scala.concurrent.EvaluationContext,
context: Context): Future[Result] = ???
def processRequest = {
/* ... */
implicit val context: Context = Context(getRequestId, /* ... */)
processInAnotherThread(/* explicit parameters */)
}
缺点是每个需要访问 Context
的函数都必须有这个参数,它会在函数签名中乱扔垃圾。
2) 放入DynamicVariable
:
// Context companion object
object Context {
val context: DynamicVariable[Context] =
new DynamicVariable[Context](Context(0, /* ... */))
}
def processInAnotherThread(/* explicit arguments */)(
implicit evaluationContext: scala.concurrent.EvaluationContext
): Future[Result] = {
// get requestId from context
Context.context.value.requestId
/* ... */
}
def processRequest = {
/* ... */
Context.context.withValue(Context(getRequestId, /* ... */)) {
processInAnotherThread(/* explicit parameters */)
}
}
缺点是
- 在处理的深处并不能立即清楚是否有一些上下文可用,它有什么内容,而且参照透明性也被破坏了。我认为最好严格限制可用
DynamicVariable
的数量,最好不要超过 1 个或最多 2 个并记录它们的使用。 - context 的所有内容必须具有默认值或
null
s,或者默认情况下它本身必须是null
(new DynamicVariable[Context](null)
)。在处理之前忘记初始化Context
或其内容可能会导致严重错误。
DynamicVariable
仍然比某些全局变量好得多,并且不会影响不以任何方式直接使用它的函数的签名。
在这两种情况下,您都可以使用 case class
的 copy
方法更新现有 Context
的内容。例如:
def deepInProcessing(/* ... */): Future[Result] =
Context.context.withValue(
Context.context.value.copy(someParameter = newParameterValue)
) {
processFurther(/* ... */)
}