SqlConnection.OpenAsync проблема - PullRequest
       18

SqlConnection.OpenAsync проблема

0 голосов
/ 22 февраля 2019

Я столкнулся с особой асинхронной проблемой, которую я могу легко воспроизвести, но не могу понять.

Моя текущая настройка

У меня есть служба WCF, которая предоставляет два API - API1 и API2.Оба договора на обслуживание являются синхронными.API1 ищет словарь в памяти, затем создает задачу с помощью Task.Factory.StartNew, чтобы создать новую задачу, которая получает данные с сервера SQL, сравнивает их с данными из словаря и записывает некоторые журналы.В случае, если у сервера SQl есть проблемы с подключением, он повторяет попытку SqlConnection.OpenAsync еще 3 раза.Обратите внимание, что сам вызов API возвращается, как только он получает данные из словаря (не ожидает завершения операции SQl)

API2 намного проще, он просто вызывает хранимую процедуру на сервере SQL, получаетданные и возврат.

Код для открытия соединения выглядит следующим образом:

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 недоступен даже в течение коротких периодов времени, а некоторые клиенты делают всего 100 вызовов API1:

  1. Когда мой SQLСервер имеет проблемы с подключением, и я получаю около 100 вызовов API1, хотя API1 возвращается к вызывающей стороне, он создал 100 задач, которые попытаются открыть соединение с плохой БД.Каждая из этих задач на некоторое время зависает при повторном поиске (что ожидается).В своих экспериментах я могу смоделировать недоступность БД, используя неверную строку соединения для API1.
  2. Теперь предположим, что БД снова работает, и сервис API2 обращается к сервису.Я обнаружил, что когда вызов API2 достигает части OpenAsync, описанной выше, он зависает. Просто зависает : (

Некоторые наблюдения 1. Когда я смотрю на «Параллельные стеки» из Visual Studio, я обнаруживаю, что есть 100 потоков со стеком API1, которые делают следующеестек:

 ManualResetEvenSlim.Wait()
 Task.SpinThenBlockingWait
 Task.InternalWait();
 Task<>.GetREsultCore
 OpenConn()

Существует 1 поток со стеком API2, который снова находится в стеке, аналогичном описанному выше.

Однако, если я заменю SqlConnection.OpenAsync на SqlConnection.Open(), вызов API2 возвращается немедленно.

Нужна помощь

Что я хотел бы понять, так это почему API2, которыйможет открыть соединение с БД (потому что БД в это время доступно), также зависать на OpenAsync.Есть ли какие-либо очевидные проблемы с синхронизацией, которые я вижу?Когда я изменяю SqlConnection.OpenAsync () на SqlConnection.Open (), почему меняется поведение?

...