如何将DI requestcope转移到另一个线程?

时间:2015-07-31 13:54:54

标签: c# multithreading dependency-injection ninject

背景:

我在我的Web应用程序中使用DI。 (我正在使用NInject,但希望这不重要)

某些地方的构造函数注入是不可能的,例如在我的自定义log4net数据库记录器中(不是我,实例化我的自定义记录器而不是log4net框架)。所以我在服务定位器DP模式下使用我的DI容器,并在记录器代码中明确要求实例解析。

请注意,这只是一个示例,在许多其他情况下,我不得不使用NInject作为服务定位器DP而不是构造函数注入。

现在问题: 我有一个IAuditContextProvider,它提供当前请求的审计数据,如IP等。问题出现了我如何配置我的DI容器来实例化一个具体的提供者。到目前为止,我已经使用了NINject支持的请求范围(按请求单身)。

但是最近我面临的事实是我必须开始一个请求启动的后台处理。这是由

完成的
// This is 'outside' it's actually a request serving method running in the request context, btw it is an MVC action method,
// Pseudo code:
var auditProvider = Locator.Resolve<IAuditProvider>()

Task.Run(() =>
{
     // I would like to get the very same resolved auditProvider instance here as outside.
     // Please note: outer local variables are not solution, because of implicit calls here inside, for example if there is a logging statement here, then the service locator in the custom logger must resolve the very same instance as outside

    // Some how I must 'stamp' this thread to be the 'same' as the outside 
    // request thread in point of view of my custom scope resolver (see below) 
}

注意:配置DI容器的宽范围单例不是解决方案,因为多个请求是服务器并行的,并且它们不能使用公共auditProvider。

好的,我认为这是自定义(解析)范围的用途。这是伪代码我如何配置我的DI容器:

kernel
  .Bind(typeof(IAuditContextProvider))
  .To(typeof(WebAuditContextProvider)).InScope(dummy =>
  {
       // Here I have to return a very same object/number/id when in
       // 'outside' the request thread, and inside the worker thread. 
       // This   way I hopefully get the very same instance when resolving.

       // To be short: I have no idea how?

  });

2 个答案:

答案 0 :(得分:0)

我不认为在目前的范围内对你的问题有一个很好的答案。

我确实有另一种建议 - 只需在另一个过程中同步执行工作。这需要一种形式的进程间通信(IPC),但不应该太困难。

一种简单但有效的IPC形式只是将记录写入数据库表(像队列一样),并使用Windows服务/守护程序轮询新记录到&#34;进程&#34;。在此示例中,您将在表中放置具有上下文信息(用户标识等)的记录,并且服务将利用此上下文同步执行工作,但工作流将与Web UI异步。

这也有一个很好的附带好处:您可以开始在服务中构建监控,重试逻辑等。在ASP.NET模型中,这些事情很难可靠地完成。

您可以通过使用消息队列/总线/事件等方式完全放弃数据库队列,但基本概念是相同的。

答案 1 :(得分:0)

<击>更新

<击>

您是否尝试在C#中使用闭包?像这样:

var auditProvider = Locator.Resolve<IAuditProvider>()

Task.Run(() =>
{
    // with closure you'll get that very variable you need:
    auditProvider.SomeMethod();
}

您应该阅读whole article about closures by John Skeet以及他们如何与TPL一起帮助您。

其他有用信息:
这样的DI在着名的书Dependency Injection by M. Seeman中被称为Ambient Context

  

如果您必须将实例传递给每个协作者,真正通用的CROSS-CUTTING CONCERN可能会污染应用程序的大部分API。另一种方法是定义一个可供任何需要它的人使用的上下文,并且可以被其他人忽略。

     

AMBIENT CONTEXT可通过静态属性提供给任何消费者   或方法。消费类可能会像这样使用它:

public string GetMessage() { return SomeContext.Current.SomeValue; } 
     

在这种情况下,上下文具有消费者可以访问的静态Current属性。此属性可以是真正的静态,也可以与当前正在执行的线程相关联。要在DI场景中有用,上下文本身必须是ABSTRACTION,并且必须可以从外部修改上下文 - 在前面的示例中,这意味着Current属性必须是可写的。可以实现上下文本身,如下面的清单所示。

     

上下文是一个abstract类,它允许我们在运行时用另一个实现替换一个上下文。

public abstract class SomeContext
{
    public static SomeContext Current
    {
        get
        {
            // Get current context from TLS
            var ctx = Thread.GetData(Thread.GetNamedDataSlot("SomeContext")) as SomeContext;
            if (ctx == null)
            {
                ctx = SomeContext.Default;
                Thread.SetData(Thread.GetNamedDataSlot("SomeContext"), ctx);
            }
            return ctx;
        }

        set
        {
            Thread.SetData(Thread.GetNamedDataSlot("SomeContext"), value);
        }
    }

    public static SomeContext Default = new DefaultContext();
    public abstract string SomeValue { get; }
}

TLS此处代表Thread Local Storage,这对您来说很有用。

另外,我建议您阅读有关OperationContext课程的内容,如果您想为Task传递一些上下文,可能会对您有所帮助,如下所示:

// save current context before task start
var operationContext = OperationContext.Current;
Task.Run(() =>
{
    // set current operation context inside your Task with closure
    OperationContext.Current = operationContext;
    // Your work here
}