首页 > 解决方案 > SqlConnection.OpenAsync 问题

问题描述

我面临一个特殊的异步问题,我可以轻松重现但无法理解。

我当前的设置

我有一个 WCF 服务,它公开了两个 API——API1 和 API2。两个服务合同都是同步的。API1,在内存中查找字典,然后使用 Task.Factory.StartNew 创建一个任务以创建一个新任务,该任务从 SQL 服务器获取数据,将其与字典中的数据进行比较并写入一些日志。如果 SQl 服务器有连接问题,这将重试 SqlConnection.OpenAsync 3 次。请注意,API 调用本身一有字典中的数据就会返回(不等待 SQl 操作完成)

API2 要简单得多,它只是调用 SQL Server 上的存储过程,获取数据并返回。

打开连接的代码如下:

public static int OpenSqlConn(SqlConnection connection)
{
    return OpenSqlConn(connection).Result;
}        

public async static Task<int> OpenSqlConnAsync(SqlConnection connection)
{
    return await OpenConnAsync(connection);
}

private static async Task<int> OpenConnAsync(SqlConnection connection)
{                    
    int retryCounter = 0;

    TimeSpan? waitTime = null;

    while (true)
    {
        if (waitTime.HasValue)
        {
            await Task.Delay(waitTime.Value).ConfigureAwait(false);
        }

        try
        {                
            startTime = DateTime.UtcNow;
            await connection.OpenAsync().ConfigureAwait(false);
            break;
        }               
        catch (Exception e)
        {
            if (retryCounter >= 3)
            {                        
                SafeCloseConnection(connection);
                return retryCounter;                    
            }                   

            retryCounter++;                                        
            waitTime = TimeSpan.FromSeconds(6);
        }
    }
    return retryCounter;        
}

API1 代码如下所示:

public API1Response API1 (API1Request request) 
{
    // look up in memory dictionary for the request
    API1Response response = getDataFromDictionary(request);

    // create a task to get some data from DB       
    Action action = () =>
    {
        GetDataFromDb(request);
    }   
    Task.Factory.StartNew(action).ConfigureAwait(false);

    // this is called immediately even if DB is not available and above task is retrying.
    return API1Response;
}

public void GetDataFromDb(API1Request request) 
{
    using (var connection = new SqlConnection(...)) 
    {
        OpenSqlConn(connection);
        /// hangs for long even if db is available

        ReadDataFromDb(connection);
    }
}

public API2Response API2(API2REquest request)
{
    return GetDataFromDbForAPI2(request)
}

public API2Response GetDataFromDbForAPI2(API2Request request) 
{
    using (var connection = new SqlConnection(...)) 
    {
        OpenSqlConn(connection); /// hangs for long even if db is available

        ReadDataFromDb(connection);
    }
}

问题

当 SQL Server 短时间不可用时,该服务会遇到以下问题,并且某些客户端仅对 API1 进行了 100 次调用:

  1. 当我的 SQL 服务器出现连接问题并且我收到大约 100 次 API1 调用时,即使 API1 返回给调用者,它也会创建 100 个任务,这些任务将尝试打开与坏数据库的连接。这些任务中的每一个都在重试查找中挂起一段时间(这是预期的)。在我的实验中,我可以通过对 API1 使用错误的连接字符串来模拟数据库不可用。
  2. 现在假设数据库再次备份,并且对服务进行了对 API2 的调用。我发现当 API2 调用到达上面的 OpenAsync 部分时,它会挂起。只是挂起:(

一些观察 1. 当我查看 Visual Studio 中的“并行堆栈”时,我发现 API1 堆栈有 100 个线程执行以下堆栈:

 ManualResetEvenSlim.Wait()
 Task.SpinThenBlockingWait
 Task.InternalWait();
 Task<>.GetREsultCore
 OpenConn()
  1. API2 堆栈有 1 个线程,它再次位于与上述类似的堆栈中。

  2. 但是,如果我替换SqlConnection.OpenAsyncSqlConnection.Open(),API2 调用会立即返回。

需要帮忙

我想了解的是为什么可以打开数据库连接(因为当时数据库可用)的API2也挂在OpenAsync上。我看到有任何明显的同步问题吗?当我将 SqlConnection.OpenAsync() 更改为 SqlConnection.Open() 时,为什么行为会发生变化?

标签: c#sql-serverasync-awaitsqlconnection

解决方案


推荐阅读