Проблема SqlConnection.OpenAsync

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

Моя текущая установка

У меня есть служба 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. Есть 1 поток со стеком API2, который снова находится в том же стеке, что и выше.

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

Нужна помощь

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

Если вам нужно добавить повторы/задержки к connection.OpenAsync() таким образом, у вас большие проблемы. Я бы исправил проблемы, требующие этого в первую очередь.

mxmissile 22.02.2019 16:25

Возможно, вы попали в тупик, потому что используете блокирующий вызов Task.Result в OpenSqlConn. Не смешивайте асинхронный код с Task.Result, Task.Wait или любым другим способом блокировки выполнения.

Dirk 22.02.2019 16:28

У меня к тебе вопрос. Как метод OpenAsync() сообщает об ошибке?

jdweng 22.02.2019 16:34

@jdweng - OpenAsync выдает исключение тайм-аута

shekhar 23.02.2019 08:59

@mxmissile - вы упомянули, что если вам нужно добавить повторы/задержки в connection.OpenAsync(), у вас будут большие проблемы. Можете ли вы объяснить немного больше. Я знаю, что там есть какая-то проблема, но не уверен, что. Отсюда мой вопрос о SO :)

shekhar 23.02.2019 09:00

Вот почему OpenAsycn зависает. Единственный механизм отчетов, который есть в OpenAsycn, — это тайм-аут. Метод Open() не имеет тайм-аута и быстро сообщает о проблеме.

jdweng 23.02.2019 13:55
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
0
6
665
0

Другие вопросы по теме