System.ObjectDisposedException:无法访问已处置的对象,ASP.NET Core 3.1
System.ObjectDisposedException: Cannot access a disposed object, ASP.NET Core 3.1
我正在 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()
方法是对 transaction/session 过程的概括尝试,看起来像这样:
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: Cannot access a disposed object.
Object name: 'MongoDB.Driver.Core.Bindings.CoreSessionHandle'.
at MongoDB.Driver.Core.Bindings.WrappingCoreSession.ThrowIfDisposed()
at MongoDB.Driver.Core.Bindings.WrappingCoreSession.get_IsInTransaction()
at MongoDB.Driver.ClientSessionHandle.get_IsInTransaction()
at MongoDB.Driver.MongoCollectionImpl1.CreateBulkWriteOperation(IClientSessionHandle session, IEnumerable
1 requests, BulkWriteOptions options)
at MongoDB.Driver.MongoCollectionImpl1.BulkWriteAsync(IClientSessionHandle session, IEnumerable
1 requests, BulkWriteOptions options, CancellationToken cancellationToken)
at MongoDB.Driver.MongoCollectionBase1.ReplaceOneAsync(FilterDefinition
1 filter, TDocument replacement, ReplaceOptions options, Func3 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 callback, TState& state)
at System.Threading.QueueUserWorkItemCallback.Execute()
at System.Threading.ThreadPoolWorkQueue.Dispatch()
at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
所以 session
对象似乎在 await _userCollectionService.UpdateAsync(session, existingUser.Id, existingUser);
行执行之前就过期了。 await session.CommitTransactionAsync();
行首先执行。
我哪里错了?我觉得 Task
即将到来...
好的,我找到了解决方法,尽管我对解决方案并不完全满意。
我发现了 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();
});
}
似乎可行,但这是我需要记住的每个数据库操作结束时的额外步骤。如果有人可以对此进行改进,我将很高兴听到!
您的 DatabaseAction
不是异步委托。好像是Action<T>
,其实你真正需要的是Func<T, Task>
。因此,当您执行 async session =>
时,该代码会变成 async void
签名。由于它是 void
,您不能 await
。这会导致您的 async
代码被调度到线程池线程并立即调用 returns。这具有 await session.CommitTransactionAsync()
提交的连锁反应,而您的代表可能还没有开始 运行。 ExecuteInTransaction
中的代码现在被认为是“完成”并退出,由于 using
阻塞,您的 session
沿途处理。
要解决此问题,您需要更改 DatabaseAction
的签名,然后 await databaseAction(session);
我正在 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()
方法是对 transaction/session 过程的概括尝试,看起来像这样:
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: Cannot access a disposed object. Object name: 'MongoDB.Driver.Core.Bindings.CoreSessionHandle'. at MongoDB.Driver.Core.Bindings.WrappingCoreSession.ThrowIfDisposed() at MongoDB.Driver.Core.Bindings.WrappingCoreSession.get_IsInTransaction() at MongoDB.Driver.ClientSessionHandle.get_IsInTransaction() at MongoDB.Driver.MongoCollectionImpl
1.CreateBulkWriteOperation(IClientSessionHandle session, IEnumerable
1 requests, BulkWriteOptions options) at MongoDB.Driver.MongoCollectionImpl1.BulkWriteAsync(IClientSessionHandle session, IEnumerable
1 requests, BulkWriteOptions options, CancellationToken cancellationToken) at MongoDB.Driver.MongoCollectionBase1.ReplaceOneAsync(FilterDefinition
1 filter, TDocument replacement, ReplaceOptions options, Func3 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 callback, TState& state) at System.Threading.QueueUserWorkItemCallback.Execute() at System.Threading.ThreadPoolWorkQueue.Dispatch() at System.Threading._ThreadPoolWaitCallback.PerformWaitCallback()
所以 session
对象似乎在 await _userCollectionService.UpdateAsync(session, existingUser.Id, existingUser);
行执行之前就过期了。 await session.CommitTransactionAsync();
行首先执行。
我哪里错了?我觉得 Task
即将到来...
好的,我找到了解决方法,尽管我对解决方案并不完全满意。
我发现了 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();
});
}
似乎可行,但这是我需要记住的每个数据库操作结束时的额外步骤。如果有人可以对此进行改进,我将很高兴听到!
您的 DatabaseAction
不是异步委托。好像是Action<T>
,其实你真正需要的是Func<T, Task>
。因此,当您执行 async session =>
时,该代码会变成 async void
签名。由于它是 void
,您不能 await
。这会导致您的 async
代码被调度到线程池线程并立即调用 returns。这具有 await session.CommitTransactionAsync()
提交的连锁反应,而您的代表可能还没有开始 运行。 ExecuteInTransaction
中的代码现在被认为是“完成”并退出,由于 using
阻塞,您的 session
沿途处理。
要解决此问题,您需要更改 DatabaseAction
的签名,然后 await databaseAction(session);