DbContext 在使用异步任务方法时被释放

Posted

技术标签:

【中文标题】DbContext 在使用异步任务方法时被释放【英文标题】:DbContext Gets Disposed Upon Using async Task method 【发布时间】:2019-01-08 13:58:57 【问题描述】:

我目前正在使用异步任务方法来实现 IAuthenticationFilter 接口。成功登录后,我将尝试访问具有此属性的 API,它会正常工作。但是,一旦我返回并再次访问 API,就会抛出异常。

public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)

    var token = context.Request.Headers.Authorization.Parameter;
    var principal = await AuthenticateToken(token)
    // Other code here ... 


protected Task<IPrincipal> AuthenticateToken(string token)

    var secretKey = _authenticationBusiness.GetSecretKey(); // error triggers here.

    if (principal == null)
        context.ErrorResult = new AuthenticationFailureResult("Invalid token", request);
    else
        context.Principal = principal;


//AuthenticationBusiness.cs
public string GetSecretKey()

    using (_unitOfWork)
    
        var token = _unitOfWork.Tokens.GetToken();

        return token.SecretKey ?? string.Empty;
    


//Dependency Injection using Unity
    container.RegisterType<IUnitOfWork, UnitOfWork>(new HierarchicalLifetimeManager());
    container.RegisterType<IContext, Context>(new HierarchicalLifetimeManager());

//UnitOfWork.cs
private readonly IContext _context;

public UnitOfWork(IContext context, IJWTRepository tokens)

    _context = context;
    Tokens = tokens;


public IJWTRepository Tokens  get; private set; 

public void Dispose()

    _context.Dispose();



//Context.cs
public class Context : DbContext, IContext

    public new void SaveChanges()
    
        base.SaveChanges();
    

    public new void Dispose()
    
        base.Dispose();
    


//JWTRepository.cs
public class JWTRepository : Repository<JsonWebToken>, IJWTRepository

    public JWTRepository(Context context) : base(context)  

    public JsonWebToken GetToken()
    
        return Context.Tokens
            .OrderBy(jwt => jwt.Id)
            .Take(1)
            .SingleOrDefault();
    

    private Context Context => _context as Context;

如果我尝试删除此属性并多次访问 API 并没有发生任何错误,所以我假设这与该属性具有异步方法这一事实有关?

【问题讨论】:

因为你正在处理它。您的 GetSecretKey() 被包裹在 using() 中,这将处理 _unitOfWork using() 本质上将在项目超出范围后处理该项目。如果您正在执行其他任何异步操作(我假设您返回任务等),您可能还希望将您的 CancellationToken 传递给您的 AuthenticateToken() 方法。 @JohanP:如果处理是问题,当我删除这个属性时它怎么能正常工作?这意味着如果我尝试在没有 IAuthenticationFilter 的情况下多次访问 API 一切正常。 @GlennWatson:AuthenticateToken 方法中没有异步方法。我更新了方法。 当你多次访问它时,你会得到一个新的上下文,你的 DI 会为每个请求注入一个新的,当你有过滤器时,上下文仍然是请求中的那个,你在一个请求中多次访问 unitOfWork。 【参考方案1】:

当 IDisposable 对象的生命周期被限制为单个 方法,你应该在 using 语句中声明和实例化它。 using 语句调用对象中的 Dispose 方法 正确的方法,并且(当您如前所示使用它时)它也会导致 一旦调用 Dispose,对象本身就会超出范围。之内 using 块,该对象是只读的,不能修改或 重新分配。

using Statement (C# Reference)

在您的代码中,问题在于您将 GetSecretkey() 包装到 using() 中,这将处理 _unitOfWork 并且当您再次尝试访问它时会显示错误。

希望此代码对您有用。

//AuthenticationBusiness.cs

public string GetSecretKey()

    var token = _unitOfWork.Tokens.GetToken();

    return token.SecretKey ?? string.Empty;

【讨论】:

它就像一个魅力。但是,如果抛出异常,我将如何正确处理 _unitOfWork,比如_unitOfWork.Tokens.GetToken(); @JoshMonreal 你的 DI 会为你处理它,经验法则是创建对象的东西负责处理它。【参考方案2】:

你的函数AuthenticateToken中的问题。使用 async-await 时,如果 Task Dispose 项目,请确保在返回之前等待每个 Task。见what is the purpose of return await。第一个答案集中在一次性物品上

我假设您省略了方法AuthenticateToken 的部分内容,因为我没有说返回值。

解决方法:声明方法async,等待Task返回

async Task<IPrincipal> AuthenticateToken(string token)

    var secretKey = _authenticationBusiness.GetSecretKey(); 
    ...
    // Somewhere there is a Task involved, 
    Task<IPrincipal> myTask = ...

    // instead of return myTask:
    return await myTask;

【讨论】:

以上是关于DbContext 在使用异步任务方法时被释放的主要内容,如果未能解决你的问题,请参考以下文章

从 Task.WhenAll 调用的异步方法使用 DbContext 并返回错误

在 C# 中将锁转换为异步

以线程安全的方式使用 DbContext 进行异步搜索

使用异步任务将数据显示到列表视图时强制关闭应用程序

C#异步编程概念和使用

020 异步任务