Приложение winforms c# 4.8, подключенное к Firebird 2.5 зависает, а сервер передает ошибку 10054
Недавно мы обновили версию приложения с Net 4.0 => 4.8, провайдер Firebird 2.1.0.0 => 2.7.0.0, заменили передачу типа транзакции FbTransactionOptions.ReadCommitted => System.Data.IsolationLevel.ReadCommited, а также вынесли выполнение крупных и продолжительных(5+ сек) select запросов в фоновые задачи. Теперь у некоторых пользователей стало навечно зависать приложение, оно поедает ресурсы и нет свойственного эффекта от зависания (Не отвечает), как будто происходит взаимоблокировка потоков, о чем также свидетельствуют дампы памяти зависших приложений. Причем зависание происходит не в момент непосредственной работы с приложением, а после непродолжительного простоя(от пары минут и более), когда им даже не пользовались. У кого-то при выходе из спящего режима, а у кото-то и без него, но уже после того, как все фоновые загрузки завершились. Таймеров и прочего, что могло запуститься в работу спустя время на формах нет. Сервер в логах в момент зависания регистрирует ошибку 10054, хотя он и до обновления регистрировал эти ошибки(неполадок ранее это не вызывало). Ошибка скорее всего связана с нестабильным сетевым подключением по неким ИЗВЕСТНЫМ причинам, но данная ошибка вызывает зависание лишь у некоторых пользователей(причем у них зависание происходит каждый раз при возникновении этой ошибки(на Windows 10, 11 и по-моему 7)).
Основное подключение к базе идет со включенным пулом потоков, оно открывается с момента запуска приложения и закрывается при выходе. Фоновые подключения идут с выключенным пулом потоков.
Вот пример фоновых загрузок:
CancellationTokenSource token = null;
public bool IsBusy => token != null;
private void SomeFunc()
{
if (IsBusy)
return;
token = new CancellationTokenSource();
Task.Run(() =>
{
FbConnection con = null;
try
{
Action action = () =>
{
// Обязательные действия до выполнения полезной нагрузки
};
if (InvokeRequired)
Invoke(action);
else
action();
con = GetTempCon(); // Тут просто копируется текст подключения основного подключения, добавляется приставка Pooling=false и открывается соединение
// Полезная нагрузка
token.Token.ThrowIfCancellationRequested();
if (this.IsHandleCreated)
{
action = () =>
{
// Действия при успешном выполнении полезной нагрузки
};
if (InvokeRequired)
Invoke(action);
else
action();
}
}
catch (OperationCanceledException)
{
// Если операция отменена
return;
}
catch (Exception exc)
{
// Обработка ошибок
}
finally
{
if (con != null)
{
if (con.State != ConnectionState.Closed)
con.Close();
con.Dispose();
con = null;
}
if (token != null)
{
token.Dispose();
token = null;
}
if (this.IsHandleCreated)
{
Action action = () =>
{
// Обязательные действия после выполнения полезной нагрузки
};
if (InvokeRequired)
Invoke(action);
else
action();
}
}
});
}
Мы пробовали откатить версию провайдера, тип транзакции и Net с заменой вызовов Task.Run на Task.Factory.StartNew, но ничего не помогло. В будущем мы планируем переход на новую версию Firebird, но решение нужно сейчас.
Ответы (1 шт):
Налицо плавающий дедлок, но глядя на обрезанный код без даже кода вызывающего метода, здесь мало что можно сказать. Для нормального анализа нужно всё: код вызова начиная с обработчика события или команды, заканчивая всеми дочерними функциями. В противном случае это угадайка.
Одно могу сказать, этот код можно без единого инвока написать, полностью асинхронно, отвязав от интерфейса совсем, что даже вероятные дедлоки не будут вешать гуй, но начну с простого - избавлюсь от инвоков.
private CancellationTokenSource cts = null;
public bool IsBusy => cts != null;
private async Task SomeFuncAsync()
{
using (cts = new CancellationTokenSource())
{
// Обязательные действия с UI до выполнения полезной нагрузки
try
{
await Task.Run(() =>
{
using (FbConnection con = GetTempCon())
{
// Полезная нагрузка
cts.Token.ThrowIfCancellationRequested();
// Ещё полезная нагрузка
}
cts.Token.ThrowIfCancellationRequested();
});
// Действия с UI при успешном выполнении полезной нагрузки
}
catch (OperationCanceledException) { }
catch (Exception exc)
{
// Обработка ошибок
}
// Обязательные действия с UI после выполнения полезной нагрузки
}
cts = null;
}
Вообще без инвоков.
Вызывающий код, позволяющий организовать асинхронный вызов, будет выглядеть так:
private async void Button_Click(object sender, EventArgs e)
{
if (IsBusy)
return;
Button btn = (Button)sender;
btn.Enabled = false;
await SomeFuncAsync();
btn.Enabled = true;
}
Такое исполнение обработчика события допустимо только если есть гарантия, что SomeFuncAsync
не выбросит исключение ни при каких обстоятельствах. В противном случае, его требуется обернуть в try-catch
с перехватом Exception
.
Обратите внимание, что метод SomeFuncAsync
предназначен только для вызова в контексте UI потока, то есть вот так await Task.Run(SomeFuncAsync)
делать нельзя.