简体   繁体   English

调试 scala 期货 - 如何确定未来的执行上下文

[英]debugging scala futures - how to determine the future's execution context

related with another question I posted ( scala futures - keeping track of request context when threadId is irrelevant ) when debugging a future, the call stack isn't very informative (as the call context is usually in another thread and another time).与我发布的另一个问题( scala futures - 在 threadId 无关跟踪请求上下文)相关,在调试未来时,调用堆栈的信息量不是很大(因为调用上下文通常在另一个线程和另一个时间)。 this is especially problematic when there can be different paths leading to the same future code (for instance usage of DAO called from many places in the code etc).当可能有不同的路径导致相同的未来代码时,这尤其成问题(例如使用从代码中的许多地方调用的 DAO 等)。 do you know of an elegant solution for this?你知道一个优雅的解决方案吗? I was thinking of passing a token/request ID (for flows started by a web server request) - but this would require passing it around - and also won't include any of the state which you can see in the stack trace.我正在考虑传递令牌/请求 ID(对于由 Web 服务器请求启动的流) - 但这需要传递它 - 并且也不包括您可以在堆栈跟踪中看到的任何状态。 perhaps passing a stack around?也许传递一个堆栈? :) :)

Suppose you make a class假设你创建一个类

case class Context(requestId: Int, /* other things you need to pass around */)

There are two basic ways to send it around implicitly:有两种基本方法可以隐式发送它:

1) Add an implicit Context parameter to any function that requires it: 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 */)
} 

The drawback is that every function that needs to access Context must have this parameter and it litters the function signatures quite a bit.缺点是每个需要访问Context的函数都必须有这个参数,而且它会大量占用函数签名。

2) Put it into a DynamicVariable : 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 */)
  }
} 

The drawbacks are that缺点是

  • it's not immediately clear deep inside the processing that there is some context available and what contents it has and also referential transparency is broken.在处理过程中并不能立即清楚是否有一些可用的上下文及其包含的内容以及引用透明度被破坏。 I believe it's better to strictly limit the number of available DynamicVariable s, preferably don't have more than 1 or at most 2 and document their use.我认为最好严格限制可用的DynamicVariable的数量,最好不要超过 1 或最多 2 并记录它们的使用。
  • context must either have default values or null s for all its contents, or it must itself be a null by default ( new DynamicVariable[Context](null) ). context 的所有内容必须具有默认值或null ,或者默认情况下它本身必须为null ( new DynamicVariable[Context](null) )。 Forgetting to initialize Context or its contents before processing may lead to nasty errors.在处理之前忘记初始化Context或其内容可能会导致严重的错误。

DynamicVariable is still much better than some global variable and doesn't influence the signatures of the functions that don't use it directly in any way. DynamicVariable仍然比某些全局变量好得多,并且不会影响不以任何方式直接使用它的函数的签名。


In both cases you may update the contents of an existing Context with a copy method of a case class .在这两种情况下,您都可以使用case classcopy方法更新现有Context的内容。 For example:例如:

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

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM