使用异步任务和拥有的实例升级 Autofac 时出现问题

Issue when upgrading Autofac with async tasks and owned instances

我的 ASP.NET MVC 应用程序从 4.9.2 升级到 5.2 后,Autofac 出现问题。 我在 Controller 中使用 Func<Owned<T>> 工厂模式,因为 Controller Action 启动一个 Long 运行ning Task 并且 运行 比请求存在的时间长。在该任务中,我正在解决其他实例。

这在 Autofac 4.9.2 中运行良好。但是在升级到 Autofac 5.2 之后,父生命周期范围 (AutofacWebRequest) 被处理掉并且不可能再在拥有的实例中解析实例。

Instances cannot be resolved and nested lifetimes cannot be created from this LifetimeScope as it (or one of its parent scopes) has already been disposed.

我可以做些什么来解决这个问题或者有最佳实践吗?

控制器代码:

private readonly Func<Owned<IBusinessLogic>> _businessLogicFactory;
public ActionResult Index()
{
    var businessLogic = _businessLogicFactory();

    var unitOfWorkFactory = _unitOfWorkFactory;
    Task.Run(() =>
    {
        System.Threading.Thread.Sleep(5000); // Sleep simulates that it may take some time until other instances are resolved
        using (businessLogic)
        {
            var task = businessLogic.Value.DoHardBusinessAsync();
            task.Wait();
        }
    });

    return View();
}

业务逻辑代码(也使用工厂):

public class BusinessLogic : IBusinessLogic
{
    private readonly Func<Owned<OtherBusinessLogic>> _otherBusinessLogicFactory;

    public BusinessLogic(Func<Owned<OtherBusinessLogic>> otherBusinessLogicFactory)
    {
        _otherBusinessLogicFactory = otherBusinessLogicFactory;
    }

    public async Task DoHardBusinessAsync()
    {
        using (var otherBusiness = _otherBusinessLogicFactory())
        {
            await otherBusiness.Value.DoHardBusinessAsync();
        }
    }
}

您可以尝试创建一个独立于请求范围的新生命周期范围,以用于您的长 运行 任务,就像这样

    Task.Run(() =>
    {
        using (var scope = container.BeginLifetimeScope())
        {
            System.Threading.Thread.Sleep(5000); // Sleep simulates that it may take some time until other instances are resolved
            using (businessLogic)
            {
               var task = businessLogic.Value.DoHardBusinessAsync();
               task.Wait();
            }
        }
    });

查看此问题以获取有关如何获取容器的想法

@NataliaMuray 的方法很棒 - 缺点 之一是它倾向于鼓励服务定位器样式解析而不是构造函数注入。这可能会“隐藏”依赖关系,从而更难识别给定 class.

的依赖关系

一个可能的解决方案是引入一个依赖项的概念,它明确地包装了另一个 依赖项,您希望在正常 Web 请求的生命周期范围之外解决该依赖项。

代码可能类似于:

public class AsyncRunner : IAsyncRunner
{
    public ExecutionResult TryExecute<TService>(Action<TService> toEvaluate, string @exceptionErrorMessage, int timeoutMilliseconds, string additionalErrorInformation = "")
    {
        try
        {
            var task = new Task(() =>
            {
                using (var scope = container.BeginLifetimeScope())
                {
                    var service = scope.Resolve<TService>();
                    toEvaluate(service);
                }
            });

            task.ContinueWith(t => { /* logging here */, TaskContinuationOptions.OnlyOnFaulted | TaskContinuationOptions.ExecuteSynchronously).SuppressExceptions();

            task.Start();
            var completedWithinTime = task.Wait(timeoutMilliseconds);
            return completedWithinTime ? ExecutionResult.Ok : ExecutionResult.TimedOut;
        }
        catch (Exception e)
        {
            /* logging here */
            return ExecutionResult.ThrewException;
        }
    }
}

同时向 Autofac 注册 IAsyncRunner。

然后是你的依赖,而不是

private readonly Func<Owned<IBusinessLogic>> _businessLogicFactory;

会是

private readonly IAsyncRunner<IBusinessLogic>> _businessLogic;

而不是:

var businessLogic = _businessLogicFactory();
var unitOfWorkFactory = _unitOfWorkFactory;
Task.Run(() =>
{
    System.Threading.Thread.Sleep(5000); // Sleep simulates that it may take some time until other instances are resolved
    using (businessLogic)
    {
        var task = businessLogic.Value.DoHardBusinessAsync();
        task.Wait();
    }
});

将是:

//var businessLogic = _businessLogicFactory();
var unitOfWorkFactory = _unitOfWorkFactory;
Task.Run(() =>
{
    System.Threading.Thread.Sleep(5000); // Sleep simulates that it may take some time until other instances are resolved

    _businessLogic.TryExecute(z => {
        var task = z.Value.DoHardBusinessAsync();
        task.Wait();
    });
});

这种风格的优点是 属性 和构造函数注入明确了依赖关系是什么,以及它们是如何被使用的(即声明清楚地表明它将在标准寿命范围)。请注意,根据我的建议,您不需要使用 Owned(处理手动构造的生命周期范围就足够了)。我已经删除了 Func 的使用,但如果您真的需要它以及我的建议,您可以使用 FuncLazy