导致这个特定方法死锁的原因

本文关键字:死锁 方法 | 更新日期: 2023-09-27 18:17:27

尽我所能,我一直选择异步。然而,我仍然坚持使用ASP。. NET成员关系,它不是为异步构建的。因此,我对string[] GetRolesForUser()等方法的调用不能使用async。

为了正确地构建角色,我依赖于来自各种来源的数据,所以我使用多个任务并行获取数据:

public override string[] GetRolesForUser(string username) {
   ...
   Task.WaitAll(taskAccounts, taskContracts, taskOtherContracts, taskMoreContracts, taskSomeProduct);
   ...
}

所有这些任务都只是使用实体框架从SQL Server数据库中获取数据。然而,最后一个任务(taskSomeProduct)的引入导致了死锁,而其他方法都没有。

下面是导致死锁的方法:

public async Task<int> SomeProduct(IEnumerable<string> ids) {
    var q = from c in this.context.Contracts
            join p in this.context.Products
            on c.ProductId equals p.Id
            where ids.Contains(c.Id)
            select p.Code;
    //Adding .ConfigureAwait(false) fixes the problem here
    var codes = await q.ToListAsync();
    var slotCount = codes .Sum(p => char.GetNumericValue(p, p.Length - 1));
    return Convert.ToInt32(slotCount);
}

然而,这个方法(看起来和所有其他方法非常相似)不会引起死锁:

public async Task<List<CustomAccount>> SomeAccounts(IEnumerable<string> ids) {
    return await this.context.Accounts
        .Where(o => ids.Contains(o.Id))
        .ToListAsync()
        .ToCustomAccountListAsync();
}

我不太确定导致死锁的方法是什么。最终,它们都在执行查询数据库的相同任务。将ConfigureAwait(false)添加到一种方法中确实可以解决问题,但我不太确定与其他执行良好的方法有何区别。

编辑

下面是一些额外的代码,我最初为了简洁而省略了:

public static Task<List<CustomAccount>> ToCustomAccountListAsync(this Task<List<Account>> sqlObjectsTask) {
    var sqlObjects = sqlObjectsTask.Result;
    var customObjects = sqlObjects.Select(o => PopulateCustomAccount(o)).ToList();
    return Task.FromResult<List<CustomAccount>>(customObjects);
}

PopulateCustomAccount方法只是从数据库的Account对象中返回一个CustomAccount对象。

导致这个特定方法死锁的原因

ToCustomAccountListAsync中调用Task.Result。这是一个典型的死锁。使用await

这不是一个答案,但我有很多话要说,它不适合评论。

一些事实: EF上下文不是线程安全的,不支持并行执行:

虽然线程安全将使异步更有用,但它是一个正交特性。目前还不清楚我们是否能够在大多数情况下实现对它的支持,因为EF与由用户代码组成的图交互以维护状态,并且没有简单的方法来确保这些代码也是线程安全的。

目前,EF将检测开发人员是否试图同时执行两个异步操作并抛出。

一些预测:

你说:

其他四个任务的并行执行已经在生产中运行了几个月,没有出现死锁。

它们不能并行执行。一种可能性是线程池不能为您的操作分配多个线程,在这种情况下,它们将被顺序执行。也可能是你初始化任务的方式,我不确定。假设它们是顺序执行的(否则您会发现我所说的异常),那么还有另一个问题:

任务。在ASP中,WaitAll与多个可等待任务挂起。净

所以也许它不是关于特定的任务SomeProduct,但它总是发生在最后一个任务?如果它们并行执行,就不会有"最后一个任务"了;但正如我已经指出的,它们一定是顺序运行的,因为它们已经在生产中运行了很长时间。