【问题标题】:Which one of these async calls is not like the other?这些异步调用中的哪一个与另一个不同?
【发布时间】:2014-07-21 12:12:35
【问题描述】:

我怀疑我遇到了死锁问题,但这是一个我无法合理化的奇怪问题。我有一个 API 需要验证一些事情才能处理调用。作为业务逻辑的一部分,我可能还需要进行更多相同的调用。在这种情况下,如果未找到与实体关联的特定数据,我们会尝试使用备份(如果已配置),这需要检查其他实体。最终,代码会挂起。

让我们深入研究代码(cmets 突出显示有问题的调用)。

API 控制器:

public async Task<HttpResponseMessage> Get(int entityID, string content, bool? useBackUp = true)
{
    //Some look-ups here, no issues at all

    //This works, but it's this method that has an issue later in the process.
    SystemEntity entityObj =
            await BusinessLayer.GetSystemEntityAsync(SystemEntityID);

    if (entityObj == null)
    {
        return new HttpResponseMessage
        {
            StatusCode = System.Net.HttpStatusCode.BadRequest,
            Content = new StringContent("Entity is unavailable.")
        };
    }

    string text = BusinessLayer.GetContentTextAsync(entityID
            new List<string> {contentName}, useBackUp).Result.FirstOrDefault().Value;

    if (text == null)
    {
        return new HttpResponseMessage {StatusCode = System.Net.HttpStatusCode.NoContent};
    }

    return new HttpResponseMessage
    {
        StatusCode = System.Net.HttpStatusCode.OK,
        Content = new StringContent(text)
    };
}

业务层:

public async Task<Dictionary<string, string>> GetContentTextAsync(int systemEntityID, List<string> contentNames, bool useBackUp)
{
    Dictionary<string, string> records = new Dictionary<string, string>();

    //We iterate for caching purposes
    foreach (string name in contentNames)
    {
        string nameCopy = name;
        string record = Cache.GetData(
            string.Format("{0}_{1}_{2}", CONTENT, systemEntityID, name), () =>
                DataLayer.GetCotnent(systemEntityID, nameCopy));

        if (record == null && useBackUp)
        {
            List<int> entityIDs = new List<int> {systemEntityID};
            int currentEntityID = systemEntityID;

            //Here's that method again. This call seems to work.
            SystemEntity currentEntity = await GetSystemEntityAsync(systemEntityID);

            if (currentEntity != null && currentEntity.BackUpID.HasValue)
            {
                currentEntityID = (int) currentEntity.BackUpID;
            }

            while (!entityIDs.Contains(currentEntityID))
            {
                int id = currentEntityID;

                record = Cache.GetData(
                    string.Format("{0}_{1}_{2}", CONTENT, systemEntityID, name), () =>
                        DataLayer.GetCotnent(id, nameCopy));

                if (record != null) break;

                entityIDs.Add(currentEntityID);

                //This call seems to cause the deadlock
                currentEntity = await GetSystemEntityAsync(currentEntityID);

                if (currentEntity != null && currentEntity.BackUpID.HasValue)
                {
                    currentEntityID = (int) currentEntity.UseBackupID;
                }
            }
        }

        if (record != null)
        {
            records.Add(name, record);
        }
    }

    return records;
}

public async Task<SystemEntity> GetSystemEntityAsync(int systemEntityID)
{
    SystemEntity systemEntity = await DataLayer.GetSystemEntity(
        scc => scc.SystemEntityID == systemEntityID);

    return systemEntity;
}

数据层:

public async Task<SystemEntity> GetSystemEntity(Expression<Func<SystemEntity, bool>> whereExpression)
{
    using (EntityContext dbContext = createDbInstance())
    {
        //This is the last line that the debugger in VS 2013 brings me to. Stepping into this returns to whatever called the API method, waiting endlessly.
        return await
            dbContext.SystemEntity.Include(sc => sc.OtherEntity).Where(whereExpression).FirstOrDefaultAsync();
    }
}

回顾一下:我调用了 GetSystemEntityAsync 三次。前两次,成功完成。第三次,挂了。如果我注释掉前两个调用,使它们根本不运行,第三个调用仍然挂起。如果我删除 await 并在数据层方法的 return 语句中只使用普通的 FirstOrDefault ,那么一切都完成得很好。

注意:我必须保持 GetSystemEntityAsync 方法是异步的。我无法将其更改为同步。

我遇到的死锁可能来自哪些方面?我不知道如何解决它。

【问题讨论】:

    标签: c# entity-framework async-await


    【解决方案1】:

    这些异步调用中的哪一个与另一个不同?

    这个,我怀疑:

      string text = BusinessLayer.GetContentTextAsync(entityID
                new List<string> {contentName}, useBackUp).Result.FirstOrDefault().Value;
    

    尝试将其更改为:

      string text = (await BusinessLayer.GetContentTextAsync(entityID
                new List<string> {contentName}, useBackUp)).FirstOrDefault().Value;
    

    Stephen Cleary 在他的"Don't Block on Async Code" 博客文章中描述了可能的死锁来源。

    【讨论】:

    • 呵呵,我没想到那条线会阻塞。 FirstOrDefaultAsync 在该上下文中似乎不存在。但是,等待它进入字典然后检查字典以决定返回什么就可以了。谢谢。
    • @Ellesedil,很高兴它有帮助。我只是假设FirstOrDefaultAsync 可能适用,显然不是。无论如何,请注意代码中其他任何地方的.Result.Wait()
    猜你喜欢
    • 2014-11-27
    • 1970-01-01
    • 2022-06-29
    • 1970-01-01
    • 2011-02-20
    • 2020-09-18
    • 1970-01-01
    • 2014-09-13
    • 2020-03-04
    相关资源
    最近更新 更多