【问题标题】:System.ObjectDisposedException: Cannot access a disposed object, ASP.NET Core 3.1System.ObjectDisposedException:无法访问已处置的对象,ASP.NET Core 3.1
【发布时间】:2020-10-20 18:44:58
【问题描述】:

我正在使用 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.MongoCollectionImpl1.CreateBulkWriteOperation(IClientSessionHandle session, IEnumerable1 请求,BulkWriteOptions 选项) 在 MongoDB.Driver.MongoCollectionImpl1.BulkWriteAsync(IClientSessionHandle session, IEnumerable1 请求,BulkWriteOptions 选项,CancellationToken cancelToken) 在 MongoDB.Driver.MongoCollectionBase1.ReplaceOneAsync(FilterDefinition1 过滤器,TDocument 替换,ReplaceOptions 选项,Func3 bulkWriteAsync) at IndistinctChatter.API.Services.Business.Profile.&lt;&gt;c__DisplayClass5_0.&lt;&lt;UpdateProfile&gt;b__0&gt;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.&lt;&gt;c.&lt;ThrowAsync&gt;b__139_1(Object state) at System.Threading.QueueUserWorkItemCallback.&lt;&gt;c.&lt;.cctor&gt;b__6_0(QueueUserWorkItemCallback quwi) at System.Threading.ExecutionContext.RunForThreadPoolUnsafe[TState](ExecutionContext executionContext, Action1 回调,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 来了……

【问题讨论】:

  • ExecuteInTransaction 更改为 async Task 并在您的 Hub 方法中等待它。提示:尽可能避免使用async void。旁注,尽量避免在您的 Hub 方法中使用 object,您应该能够通过在 Hub 方法签名中接受 ProfileDTO 来处理丑陋的 ((JsonElement) profileDto).ToObject&lt;ProfileDTO&gt;(); 代码。

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


【解决方案1】:

您的DatabaseAction 不是异步委托。它似乎是Action&lt;T&gt;,但您真正需要的是Func&lt;T, Task&gt;。因此,当您执行async session =&gt; 时,该代码将变成async void 签名。由于它是void,你不能await 它。这会导致您的async 代码被安排到线程池线程并且调用立即返回。这会产生await session.CommitTransactionAsync() 提交的连锁反应,而您的委托甚至还没有开始运行。您在ExecuteInTransaction 中的代码现在被视为“完成”并退出,由于using 块而沿途丢弃您的session

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

【讨论】:

  • 这听起来很棒,但我不确定我需要进行哪些代码更改。如何更改DatabaseAction 的签名,以及如何更改?我可能不得不请你把代码用勺子喂给我,因为你告诉我的很多东西都是外来的。 Func&lt;T, Task&gt; 例如!请帮忙!
  • 顺便说一下,我的 DatabaseAction 代表看起来像这样:public delegate void DatabaseAction(IClientSessionHandle session, AutoResetEvent autoResetEvent);
  • 我解决了!我将我的DatabaseAction 签名更改为public delegate Task DatabaseAction(IClientSessionHandle sessiont);,然后只是awaited databaseAction(session)。谢谢你的帮助!但我想知道Func&lt;t, Task&gt;。目前我正在学习很多 Pluralsight,我相信 C# 课程中会用到。
  • Func&lt;T, Task&gt; 是一个内置委托,基本上就是您手动创建的委托。因此,您的方法可以只接受Func&lt;IClientSessionHandle, Task&gt;,而不是使用DatabaseAction
  • 是的,我在最后十分钟内解决了这个问题。谢谢,@JohanP!
【解决方案2】:

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

我发现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();
  });
}

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

【讨论】:

    猜你喜欢
    • 2023-03-23
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-04-28
    • 1970-01-01
    • 2018-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多