c# - 是否可以在简单注入(inject)器的方法中使用线程范围的生活方式

标签 c# .net multithreading dependency-injection simple-injector

我有以下代码:

public class TempForm : Form
{
    private readonly IGoogleAuth _googleAuth;
    private readonly IComAssistant _comAssistant;

    public TempForm(IGoogleAuth googleAuth, IComAssistant comAssistant)
    {
        _googleAuth = googleAuth;
        _comAssistant = comAssistant;

        InitializeComponent();
    }

    private void ButtonClick(object sender, EventArgs e)
    {
        var excelThread = new Thread(() =>
        {
            //NEED NEW INSTANCE OF EXCEL_APP PER THREAD
            using (IExcelApp excel = new ExcelApp(_comAssistant))
            {
                //Do stuff with excel.
                excel.CreateWorkBook();
                //...
            }
        });

        excelThread.SetApartmentState(ApartmentState.STA);
        excelThread.Start();
    }

    private void InitializeComponent()
    {
        //Initialize form components
    }
}

我对 IGoogleAuthIComAssistant 服务没有任何问题,因为它们在容器中注册为 Singletone,并且我将它们注入(inject)到表单构造函数中。

但在 ButtonClick 方法中,我需要为每个新线程创建新的 ExcelApp 实例。

我可以这样做:

using (ThreadScopedLifestyle.BeginScope(container)) {
    var excel = container.GetInstance<IExcelApp>();
}

但是通过这种方式,我需要将在 Program.cs 中声明的 container 传递到我的 TempForm 表单中。

是否可以在不传递容器本身的情况下实现这种行为?

如果不是 - 在多个地方使用 container 实例的最佳实践是什么。 我们需要将其作为单例,还是将它们放在自己的 ServiceLocator 实现中?

谢谢。

最佳答案

Is it possible to achive such behavior without passing container itself?

是的,这当然是可能的。诀窍是将此逻辑从您的 Form 组件中提取到它自己的组件中。换句话说,您创建了一个 Aggregate Service .例如:

public class TempForm : Form
{
    private readonly IGoogleAuth _googleAuth;
    private readonly IExcelExporter _exporter;

    public TempForm(IGoogleAuth googleAuth, IExcelExporter exporter)
    {
        _googleAuth = googleAuth;
        _exporter = exporter;

        InitializeComponent();
    }

    private void ButtonClick(object sender, EventArgs e)
    {
        _exporter.Export(...);
    }

    private void InitializeComponent()
    {
        //Initialize form components
    }
}

在这里,我们将与生成 excel 文档相关的所有代码从 Form 提取到它自己的组件中。

这样的实现可能如下所示:

public class ExcelExporter : IExcelExporter
{
    private readonly IComAssistant _comAssistant;

    public ExcelExporter(IComAssistant comAssistant)
    {
        _comAssistant = comAssistant;
    }

    private void Export(...)
    {
        //NEED NEW INSTANCE OF EXCEL_APP PER THREAD
        using (IExcelApp excel = new ExcelApp(_comAssistant))
        {
            //Do stuff with excel.
            excel.CreateWorkBook();
            //...
        }
    }
}

注意这个组件本身是如何没有线程概念的。线程是该组件不应该负责的问题。将此排除在此类之外会使该类更易于理解和测试。

但这确实意味着我们必须在某个地方实现这个线程逻辑。然而,我们希望将其排除在表单和 ExcelExporter 之外。 .在执行此操作时,我们需要引用 Container .

需要访问 Container 的每段代码应该集中在应用程序的启动代码中,也就是 Composition Root .

将此线程行为添加到我们新的 ExcelExporter 的有效方法组件是通过使用 IExcelExporter 附近的代理:

public class BackgroundExcelExporterProxy : IExcelExporter
{
    private readonly Container _container;
    private readonly Func<IExcelExporter> _excelExporterFactory;

    public ExcelExporter(
        Container container, Func<IExcelExporter> excelExporterFactory)
    {
        _container = container;;
        _excelExporterFactory = excelExporterFactory;
    }

    private void Export(...)
    {
        var excelThread = new Thread(() =>
        {
            using (ThreadScopedLifestyle.BeginScope(container)) 
            {
                var exporter = _excelExporterFactory();
                exporter.Export(...);
            }
        });

        excelThread.SetApartmentState(ApartmentState.STA);
        excelThread.Start();
    }
}

此类依赖于 Container .当Export被调用,它将开始一个新的Thread在该线程内,它将启动一个新的线程范围。在该线程范围内,它将解析一个新的 IExporter及其依赖项。

当使用 RegisterDecorator 在 Simple Injector 中注册此类时方法(就 Simple Injector 而言,这是一个装饰器),Simple Injector 将 native 理解 Func<IExcelExporter>依赖关系,并且会理解此委托(delegate)应解析装饰实例的实例(在您的情况下为 ExcelExporter)。

我们可以这样注册:

container.Register<IExcelExporter, ExcelExporter>();
container.RegisterDecorator<IExcelExporter, BackgroundExcelExporterProxy>(
    Lifestyle.Singleton);

这将产生以下对象图:

new TempForm(
    MyGoogleAuth(...),
    new BackgroundExcelExporterProxy(
        container,
        () => new ExcelExporter(new MyComAssistant(...))));

We need to make it as singleton, or put them in own ServiceLocator implementation?

您可能认为 BackgroundExcelExporterProxy有一个服务定位器,但只要这个类驻留在 inside Composition Root,它就不是服务定位器,如 here 所解释的那样.

关于c# - 是否可以在简单注入(inject)器的方法中使用线程范围的生活方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/43979133/

相关文章:

c# - 扩展字典的属性不会在序列化中显示

c# - 如何在退出 WPF 应用程序时禁用清除剪贴板?

c# - 多个项目的单个缓存对象

java - 为什么 Vert.x 为 http 服务器创建一个新的事件循环?

c# - WPF 线程和 Dispatcher.Invoke

python - python中的多线程真的需要同步吗?

c# - 如何创建 .NET 软件的试用版?

C# 对象编辑和序列化初始化

c# - 如何从 float 复制一个值? float ,如果 float 则置零?一片空白?

.net - WCF REST入门套件是否在水中死了?