来自文档阅读隔离一致性 - 会话(强调我的)
为了提供因果一致性,MongoDB 3.6 在客户端会话中启用因果一致性。因果一致的会话表示关联的读取和已确认的写入操作序列具有通过其顺序反映的因果关系。 应用程序必须确保客户端会话中一次只有一个线程执行这些操作。
来自文档 交易 - 交易和会话
交易与会话相关联。也就是说,您为一个会话启动一个事务。在任何给定时间,一个会话最多可以有一个未结交易。
这是否表明使用 async/await 是不安全的,或者至少所有使用会话和事务的任务都应该以某种方式在同一个线程上执行?
还是说会话上的每个异步操作都需要完成才能开始另一个异步操作?
或者它是否说可以针对会话运行多个异步操作,但所有这些操作必须在同一个线程上运行。
tl;博士
在事务中使用 async/await 安全吗?
如果没有,这里的最佳做法是什么?
例如这是(诚然可怕的代码)可以吗?
[HttpPost]
public async Task<IActionResult> PostAsync(CreateRequest createRequest)
{
using (var session = await _client.StartSessionAsync())
{
await session.StartTransactionAsync();
var inserts = new Task[] {
_colHomer.InsertOneAsync(session, createRequest.Homer),
_colMarge.InsertOneAsync(session, createRequest.Marge),
_colBart.InsertOneAsync(session, createRequest.Bart)
};
await Task.WhenAll(inserts);
await session.CommitTransactionAsync();
}
}
事务和会话被设计为是不是线程安全的。我认为上面的代码可以工作,但不能保证所有情况。 更新:但是,以常规异步方式使用会话/事务绝对是可以节省的(没有任何手动任务等待和类似的逻辑)。
我们最近定期受到 MongoCommandException 的攻击,代码名称为:NoSuchTransaction,错误标签为:TransientTransactionError。经过一番调查,事实证明,这是由于同一事务中存在多个异步读取查询但没有等待它们而引起的。这是显示问题的“伪代码”。该方法在不同线程上运行多次:
using (var session = await _client.StartSessionAsync())
{
await session.StartTransactionAsync();
var docs1Task = _repositoryCollection1.GetAsync(id);
var docs2Task = _repositoryCollection2.GetManyAsync(id);
var docs1 = await docs1Task;
var docs2 = await docs2Task; // <-- MongoCommandException
await session.CommitTransactionAsync();
}
等待获取后,它们同步运行,不再发生异常。
这个问题让我怀疑线程安全,尽管我不认为这是由读取引起的。异常的实际消息是:
“命令查找失败:给定的交易号 38 与任何交易都不匹配 正在进行的交易。活跃交易数为37。”
所以交易中似乎有一些潜在的机制可以增加数字。
此问题发生在 MongoDB.Driver 版本 2.21.0 上。
我将这个答案留在这里以供参考(以及我未来的自己......),并得出结论,事务/会话确实不是线程安全的,如 dododo 所记录的那样。