System.ObjectDisposedException:无法访问已处置的对象,ASP.NET Core 3.1

时间:2020-10-20 18:44:58

标签: c# asp.net mongodb async-await signalr

我正在ASP.NET Core 3.1中使用SignalR编写API。我对.NET Core完全陌生,对于SignalR也很陌生。我在执行在事务中运行的MongoDB(Atlas)查询时遇到问题。似乎在执行查询之前,事务会话即将到期。我很确定这是一个async / await问题,但似乎无法解决。

我的Hub方法如下:

public async Task<bool> UpdateProfile(object profileDto)
{
  try
  {
    ProfileDTO profile = ((JsonElement) profileDto).ToObject<ProfileDTO>();
    _profile.UpdateProfile(profile);
    return true;
  }
  catch
  {
    return false;
  }
}

_profile.UpdateProfile()方法如下:

public void UpdateProfile(ProfileDTO profileDto)
{
  _databaseService.ExecuteInTransaction(async session =>
  {
    var profile = _mapper.Map<ProfileModel>(profileDto);
    var existingUser = (await _userCollectionService
        .FindAsync(session, user => user.Profille.Sub == profileDto.sub)
      ).FirstOrDefault();

    if (existingUser == null)
    {
      var newUser = new UserModel();
      newUser.Profille = profile;
      await _userCollectionService.CreateAsync(session, newUser);
    }
    else
    {
      existingUser.Profille = profile;
      // the error occurs on the following line
      await _userCollectionService.UpdateAsync(session, existingUser.Id, existingUser);
    }
  });
}

我的ExecuteInTransaction()方法是尝试概括事务/会话过程,如下所示:

public async void ExecuteInTransaction(DatabaseAction databaseAction)
{
  using (var session = await Client.StartSessionAsync())
  {
    try
    {
      session.StartTransaction();
      databaseAction(session);
      await session.CommitTransactionAsync();
    }
    catch (Exception e)
    {
      await session.AbortTransactionAsync();
      throw e;
    }
  }
}

我已在UpdateProfile()中指出了发生错误的那一行。完整的错误如下所示:

System.ObjectDisposedException:无法访问已处置的对象。 对象名称:“ MongoDB.Driver.Core.Bindings.CoreSessionHandle”。 在MongoDB.Driver.Core.Bindings.WrappingCoreSession.ThrowIfDisposed() 在MongoDB.Driver.Core.Bindings.WrappingCoreSession.get_IsInTransaction() 在MongoDB.Driver.ClientSessionHandle.get_IsInTransaction() 在MongoDB.Driver.MongoCollectionImpl 1.CreateBulkWriteOperation(IClientSessionHandle session, IEnumerable 1个请求中,使用BulkWriteOptions选项) 在MongoDB.Driver.MongoCollectionImpl 1.BulkWriteAsync(IClientSessionHandle session, IEnumerable 1个请求,BulkWriteOptions选项,CancellationToken cancelledToken上) 在MongoDB.Driver.MongoCollectionBase 1.ReplaceOneAsync(FilterDefinition 1过滤器,TDocument替换,ReplaceOptions选项,Func 3 bulkWriteAsync) at IndistinctChatter.API.Services.Business.Profile.<>c__DisplayClass5_0.<<UpdateProfile>b__0>d.MoveNext() in /Volumes/Data/Users/marknorgate/Documents/Development/source/indistinct-chatter/api-dotnet/Services/Business/Profile.cs:line 48 --- End of stack trace from previous location where exception was thrown --- at System.Threading.Tasks.Task.<>c.<ThrowAsync>b__139_1(Object state) at System.Threading.QueueUserWorkItemCallback.<>c.<.cctor>b__6_0(QueueUserWorkItemCallback quwi) at System.Threading.ExecutionContext.RunForThreadPoolUnsafe[TState](ExecutionContext executionContext, Action 1回调,TState和状态下) 在System.Threading.QueueUserWorkItemCallback.Execute() 在System.Threading.ThreadPoolWorkQueue.Dispatch() 在System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()

因此,似乎session对象在await _userCollectionService.UpdateAsync(session, existingUser.Id, existingUser);行执行之前已过期。第await session.CommitTransactionAsync();行首先执行。

我要去哪里错了?我感到Task即将来临...

2 个答案:

答案 0 :(得分:1)

您的DatabaseAction不是异步委托。好像是Action<T>,但您真正需要的是Func<T, Task>。因此,当您执行async session =>时,该代码将变成async void签名。由于它是void,因此无法await。这导致您的async代码被调度到线程池线程,并且调用立即返回。这会产生await session.CommitTransactionAsync()提交的连锁反应,而您的委托甚至可能尚未开始运行。您的ExecuteInTransaction中的代码现在被视为“完成”并退出,由于session的阻塞而将您的using丢弃。

要解决此问题,您需要先更改DatabaseAction的签名,然后再更改await databaseAction(session);

答案 1 :(得分:0)

好吧,尽管我对解决方案并不完全满意,但我已经找到了解决方法。

我发现了AutoResetEvent(),所以修改了我的代码,如下所示:

public async void ExecuteInTransaction(DatabaseAction databaseAction)
{
  AutoResetEvent autoResetEvent = new AutoResetEvent(false);

  using var session = await Client.StartSessionAsync();

  try
  {
    session.StartTransaction();
    databaseAction(session, autoResetEvent);
    autoResetEvent.WaitOne();
    await session.CommitTransactionAsync();
  }
  catch (Exception e)
  {
    await session.AbortTransactionAsync();
    throw e;
  }
}

public void UpdateProfile(ProfileDTO profileDto)
{
  _databaseService.ExecuteInTransaction(async (session, autoResetEvent) =>
  {
    var profile = _mapper.Map<ProfileModel>(profileDto);
    var existingUser = (await _userCollectionService
        .FindAsync(session, user => user.Profille.Sub == profileDto.sub)
      ).FirstOrDefault();

    if (existingUser == null)
    {
      var newUser = new UserModel();
      newUser.Profille = profile;
      await _userCollectionService.CreateAsync(session, newUser);
    }
    else
    {
      existingUser.Profille = profile;
      await _userCollectionService.UpdateAsync(session, existingUser.Id, existingUser);
    }

    autoResetEvent.Set();
  });
}

似乎可以正常工作,但这是我需要记住的每个数据库操作结束时的额外步骤。如果有人可以对此进行改进,我将很高兴听到它!