是否可以在Simple Injector的方法中使用Thread Scoped Lifestyle

Ros*_*sky 4 .net c# 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
    }
}
Run Code Online (Sandbox Code Playgroud)

我没有问题IGoogleAuthIComAssistant服务,因为它们Singletone在容器中注册,我在表单构造函数中注入它们.

但是在ButtonClick方法中我需要ExcelApp每个新线程的新实例.

我能这样做:

using (ThreadScopedLifestyle.BeginScope(container)) {
    var excel = container.GetInstance<IExcelApp>();
}
Run Code Online (Sandbox Code Playgroud)

但是这样我需要将container声明的内容传递Program.cs给我的TempForm表单.

是否有可能在不通过容器本身的情况下实现这种行为?

如果不是 - container在几个地方使用实例的最佳做法是什么.我们需要将它作为单例,或者将它们放在自己的ServiceLocator实现中?

谢谢.

Ste*_*ven 5

是否有可能在不通过容器本身的情况下实现这种行为?

是的,这当然是可能的.诀窍是将这个逻辑从Form组件中提取到它自己的组件中.换句话说,您创建聚合服务.例如:

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
    }
}
Run Code Online (Sandbox Code Playgroud)

在这里,我们将与生成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();
            //...
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

注意这个组件本身没有线程概念.线程是该组件不应负责的问题.将此课程排除在本课程之外,使课程更容易理解,更容易测试.

但这确实意味着我们必须在某处实现这种线程逻辑.但是,我们希望将其从表格中删除ExcelExporter.在这样做时,我们需要参考Container.

需要访问的每段代码Container都应集中在应用程序的启动代码中,即组合根.

将此线程行为添加到我们的新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();
    }
}
Run Code Online (Sandbox Code Playgroud)

这个类依赖于Container.当Export调用它时,它将启动一个新的,Thread并在该线程内它将启动一个新的线程范围.在该线程范围内,它将解析IExporter具有其依赖项的新内容.

当使用该RegisterDecorator方法在Simple Injector中注册此类时(就Simple Injector而言,这是一个装饰器),Simple Injector将原生地理解Func<IExcelExporter>依赖关系,并将理解该委托应解析装饰实例的实例(ExcelExporter在您的案件).

我们可以注册如下:

container.Register<IExcelExporter, ExcelExporter>();
container.RegisterDecorator<IExcelExporter, BackgroundExcelExporterProxy>(
    Lifestyle.Singleton);
Run Code Online (Sandbox Code Playgroud)

这将产生以下对象图:

new TempForm(
    MyGoogleAuth(...),
    new BackgroundExcelExporterProxy(
        container,
        () => new ExcelExporter(new MyComAssistant(...))));
Run Code Online (Sandbox Code Playgroud)

我们需要将它设为单例,还是将它们放在自己的ServiceLocator实现中?

你可能会认为,BackgroundExcelExporterProxy有一个服务定位器,但只要这个类所在成分根,它不是一个服务定位,为解释在这里.