调试 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 的所有内容必须具有默认值或 nulls,或者默认情况下它本身必须是 null (new DynamicVariable[Context](null))。在处理之前忘记初始化 Context 或其内容可能会导致严重错误。

DynamicVariable 仍然比某些全局变量好得多,并且不会影响不以任何方式直接使用它的函数的签名。


在这两种情况下,您都可以使用 case classcopy 方法更新现有 Context 的内容。例如:

def deepInProcessing(/* ... */): Future[Result] =
  Context.context.withValue(
    Context.context.value.copy(someParameter = newParameterValue)
  ) {
    processFurther(/* ... */)
  }