知行合一|

hiwwwk

园龄:5年1个月粉丝:4关注:12

2022-09-19 23:35阅读: 853评论: 4推荐: 2

Abp在后台任务中使用仓储报错

异常信息

System.ObjectDisposedException:“Cannot access a disposed context instance. A common cause of this error is disposing a context instance that was resolved from dependency injection and then later trying to use the same context instance elsewhere in your application. This may occur if you are calling 'Dispose' on the context instance, or wrapping it in a using statement. If you are using dependency injection, you should let the dependency injection container take care of disposing context instances.

后台任务代码示例

public class AutoWorker : AsyncPeriodicBackgroundWorkerBase
{
    public AutoWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory) : base(timer, serviceScopeFactory)
    {
        timer.Period = (int)TimeSpan.FromSeconds(20).TotalMilliseconds;
    }

    protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
    {
        var bookRepository = workerContext.ServiceProvider.GetRequiredService<IBookRepository>();

        var bookQuery = await bookRepository.GetQueryableAsync();

     // 出错位置
        bookQuery = bookQuery.Where(p => p.BookName == "西游记");

        var books = await bookRepository.AsyncExecuter.ToListAsync(bookQuery);
    }
}

解决方法

DoworkAsync方法上面加上UnitOfWork特性,或者后台任务继承IUnitOfWorkEnabled接口就可以了。

public class AutoWorker : AsyncPeriodicBackgroundWorkerBase, IUnitOfWorkEnabled
{
    public AutoWorker(AbpAsyncTimer timer, IServiceScopeFactory serviceScopeFactory) : base(timer, serviceScopeFactory)
    {
        timer.Period = (int)TimeSpan.FromSeconds(20).TotalMilliseconds;
    }

    // 这里加上UnitOfWork特性。或方法继承接口IUnitOfWorkEnabled
    //[UnitOfWork]
    protected override async Task DoWorkAsync(PeriodicBackgroundWorkerContext workerContext)
    {
        var bookRepository = workerContext.ServiceProvider.GetRequiredService<IBookRepository>();

        var bookQuery = await bookRepository.GetQueryableAsync();

        bookQuery = bookQuery.Where(p => p.BookName == "西游记");

        var books = await bookRepository.AsyncExecuter.ToListAsync(bookQuery);
    }
}

问题分析

Q: 问什么加上UnitOfWork特性或实现IUnitOfWorkEnabled接口就行呢?
A:这是因为上面两种方法,会使工作单元拦截器开启工作单元。
Q: 为什么GetQueryableAsync方法不报错误呢,为什么不加工作单元就会抛出不能访问已被释放的示例的异常呢?
A:从源码开始分析
调用await bookRepository.GetQueryableAsync();这行代码的时候,会进入工作单元拦截器。(Volo.Abp.Uow模块会将指定的服务添加工作单元拦截器,而仓储BasicRepositoryBase因为继承了IUnitOfWorkEnabled接口,所以该类下所有方法都会进入以下拦截器

public class UnitOfWorkInterceptor : AbpInterceptor, ITransientDependency
{
    private readonly IServiceScopeFactory _serviceScopeFactory;

    public UnitOfWorkInterceptor(IServiceScopeFactory serviceScopeFactory)
    {
        _serviceScopeFactory = serviceScopeFactory;
    }

    public override async Task InterceptAsync(IAbpMethodInvocation invocation)
    {
        if (!UnitOfWorkHelper.IsUnitOfWorkMethod(invocation.Method, out var unitOfWorkAttribute))
        {
            await invocation.ProceedAsync();
            return;
        }

        using (var scope = _serviceScopeFactory.CreateScope())
        {
            var options = CreateOptions(scope.ServiceProvider, invocation, unitOfWorkAttribute);

            var unitOfWorkManager = scope.ServiceProvider.GetRequiredService<IUnitOfWorkManager>();

            // 判断是否有预留的工作单元
            if (unitOfWorkManager.TryBeginReserved(UnitOfWork.UnitOfWorkReservationName, options))
            {
                await invocation.ProceedAsync();

                if (unitOfWorkManager.Current != null)
                {
                    await unitOfWorkManager.Current.SaveChangesAsync();
                }

                return;
            }

         // 会进入这里,开启一个新的工作单元
            using (var uow = unitOfWorkManager.Begin(options))
            {
             // 执行
                await invocation.ProceedAsync();
                await uow.CompleteAsync();
            }
        }
    }

    private AbpUnitOfWorkOptions CreateOptions(IServiceProvider serviceProvider, IAbpMethodInvocation invocation, [CanBeNull] UnitOfWorkAttribute unitOfWorkAttribute)
    {
       // ...根据工作单元特性或其它设置判断是否开启事务
    }
}

该拦截器开启一个新的工作单元,然后进入await invocation.ProceedAsync();这行代码,执行真正的GetQueryableAsync方法。

而开启工作单元的时候创建了一个新的作用域,通过该作用域获取的新的工作单元,工作单元被释放的时候,也会连带这个作用域被释放。那么我们之前通过该作用域解析出来的DbContext ,也就会随着作用域的释放而被释放。

private IUnitOfWork CreateNewUnitOfWork()
{
    // 新的作用域
    var scope = _serviceScopeFactory.CreateScope();
    try
    {
        // 忽略部分代码...
        // 通过此作用域获取的工作单元
        var unitOfWork = scope.ServiceProvider.GetRequiredService<IUnitOfWork>();
        // 忽略部分代码...
        // 工作单元释放的时候,触发释放事件,而这个事件执行的时候把当前作用域也释放了。
        unitOfWork.Disposed += (sender, args) =>
        {
            _ambientUnitOfWork.SetUnitOfWork(outerUow);
            // 重点!!!
            scope.Dispose();
        };

        return unitOfWork;
    }
    catch
    {
        scope.Dispose();
        throw;
    }
}

这就是为什么await bookRepository.GetQueryableAsync()这行代码执行成功,而bookQuery.Where(p => p.BookName == "西游记")会报错,因为bookQuery已经被释放了。
autowoker.png

具体工作单元可看:https://www.cnblogs.com/myzony/p/11112288.html
此文章中工作单元嵌套配图有错误。

具体嵌套逻辑。

// IUnitOfWorkManager.Current指向为IAmbientUnitOfWork.GetCurrentByChecking()
// 顾名思义,当前工作单元指向的是嵌套最内层的工作单元。
// 而工作单元为一个链表,Outer属性指向最新的工作单元。
var currentUow = _unitOfWorkManager.Current;

// 此处开启了新的工作单元,而周围没有新的工作单元,则开启一个新的。
using (var uow1 = _unitOfWorkManager.Begin())
{
    // 当前工作单元为 uow1,_unitOfWorkManager.Current = uow1
    // uow1.Outer指向null

    // 开启工作单元2,因为默认参数不确保开启新的工作单元(requiresNew = false),uow2为ChildUnitOfWork
    // 至于为什么返回ChildUnitOfWork,是因为防止返回uow1给uow2被直接释放,故而返回一个子工作单元
    // ChildUnitOfWork除了CompleteAsync方法为空,其余完全继承使用uow1的内容。
    using (var uow2 = _unitOfWorkManager.Begin())
    {
        // 当前工作单元为 uow1,_unitOfWorkManager.Current = uow1

        // 开启工作单元3,这里确保开启新的工作单元,所以创建新的作用域返回新的工作单元。
        using (var uow3 = _unitOfWorkManager.Begin(requiresNew: true))
        {
            // 当前工作单元为 uow3,_unitOfWorkManager.Current = uow3
            // uow3.Outer指向uow1
            await uow3.CompleteAsync();
        }
        // uow3执行完毕,释放后
        // 当前工作单元为 uow1,_unitOfWorkManager.Current = uow1
        // uow1.Outer指向null

        await uow2.CompleteAsync();
    }
    // uow2执行完毕
    // 当前工作单元为 uow1,_unitOfWorkManager.Current = uow1
    // uow1.Outer指向null

    await uow1.CompleteAsync();
}
// uow1执行完毕
// 当前工作单元为 null,_unitOfWorkManager.Current = null

本文作者:hiwwwk

本文链接:https://www.cnblogs.com/wwwk/p/16709587.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   hiwwwk  阅读(853)  评论(4编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起