Я столкнулся со своеобразной асинхронной проблемой, которую я могу легко воспроизвести, но не могу понять.
Моя текущая установка
У меня есть служба 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. Когда я смотрю на «Параллельные стеки» из 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(), почему меняется поведение?
Возможно, вы попали в тупик, потому что используете блокирующий вызов Task.Result в OpenSqlConn. Не смешивайте асинхронный код с Task.Result, Task.Wait или любым другим способом блокировки выполнения.
У меня к тебе вопрос. Как метод OpenAsync() сообщает об ошибке?
@jdweng - OpenAsync выдает исключение тайм-аута
@mxmissile - вы упомянули, что если вам нужно добавить повторы/задержки в connection.OpenAsync(), у вас будут большие проблемы. Можете ли вы объяснить немного больше. Я знаю, что там есть какая-то проблема, но не уверен, что. Отсюда мой вопрос о SO :)
Вот почему OpenAsycn зависает. Единственный механизм отчетов, который есть в OpenAsycn, — это тайм-аут. Метод Open() не имеет тайм-аута и быстро сообщает о проблеме.





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