SqlConnection OpenAsync блокирует пользовательский интерфейс при выключении SQL Server

У меня есть простое клиентское приложение WPF (.NET 4.6.2) с одной кнопкой. Метод SqlConnection.OpenAsync блокирует поток пользовательского интерфейса при выполнении следующих шагов:

  1. SQL Server запущен и доступен с клиента
  2. Запустить клиентское приложение
  3. Нажмите кнопку => пользовательский интерфейс не заблокирован (как и ожидалось) => в окне сообщения отображается «ОК»
  4. Выключить SQL-сервер
  5. Нажмите кнопку => пользовательский интерфейс заблокирован - пользовательский интерфейс блока OpenAsync => окно сообщения «показать» ошибку
  6. Нажмите еще раз на кнопку => пользовательский интерфейс снова не заблокирован => в окне сообщения отображается «ошибка»

Только если он переходит от щелчка->успех к щелчку->ошибка Поток пользовательского интерфейса блока OpenAsync. Я что-то упускаю?

private async void Button_Click(object sender, RoutedEventArgs e)
{
    try
    {
        using (var conn = new SqlConnection("...."))
        {
            await conn.OpenAsync();
        }
    }
    catch (System.Exception)
    {
        MessageBox.Show("err");
        return;
    }
    MessageBox.Show("ok");
}

Изменить:

Стек вызовов, когда я приостанавливаю отладчик во время зависания:

System.Data.dll!SNINativeMethodWrapper.SNIOpenSyncEx(SNINativeMethodWrapper.ConsumerInfo consumerInfo, string constring, ref System.IntPtr pConn, byte[] spnBuffer, byte[] instanceName, bool fOverrideCache, bool fSync, int timeout, bool fParallel, int transparentNetworkResolutionStateNo, int totalTimeout, bool isAzureSqlServerEndpoint)    Unknown
    System.Data.dll!System.Data.SqlClient.SNIHandle.SNIHandle(SNINativeMethodWrapper.ConsumerInfo myInfo, string serverName, byte[] spnBuffer, bool ignoreSniOpenTimeout, int timeout, out byte[] instanceName, bool flushCache, bool fSync, bool fParallel, System.Data.SqlClient.TransparentNetworkResolutionState transparentNetworkResolutionState, int totalTimeout)   Unknown
    System.Data.dll!System.Data.SqlClient.TdsParserStateObject.CreatePhysicalSNIHandle(string serverName, bool ignoreSniOpenTimeout, long timerExpire, out byte[] instanceName, byte[] spnBuffer, bool flushCache, bool async, bool fParallel, System.Data.SqlClient.TransparentNetworkResolutionState transparentNetworkResolutionState, int totalTimeout) Unknown
    System.Data.dll!System.Data.SqlClient.TdsParser.Connect(System.Data.SqlClient.ServerInfo serverInfo, System.Data.SqlClient.SqlInternalConnectionTds connHandler, bool ignoreSniOpenTimeout, long timerExpire, bool encrypt, bool trustServerCert, bool integratedSecurity, bool withFailover, bool isFirstTransparentAttempt, System.Data.SqlClient.SqlAuthenticationMethod authType, bool disableTnir, System.Data.SqlClient.SqlAuthenticationProviderManager sqlAuthProviderManager)  Unknown
    System.Data.dll!System.Data.SqlClient.SqlInternalConnectionTds.AttemptOneLogin(System.Data.SqlClient.ServerInfo serverInfo, string newPassword, System.Security.SecureString newSecurePassword, bool ignoreSniOpenTimeout, System.Data.ProviderBase.TimeoutTimer timeout, bool withFailover, bool isFirstTransparentAttempt, bool disableTnir)  Unknown
    System.Data.dll!System.Data.SqlClient.SqlInternalConnectionTds.LoginNoFailover(System.Data.SqlClient.ServerInfo serverInfo, string newPassword, System.Security.SecureString newSecurePassword, bool redirectedUserInstance, System.Data.SqlClient.SqlConnectionString connectionOptions, System.Data.SqlClient.SqlCredential credential, System.Data.ProviderBase.TimeoutTimer timeout)    Unknown
    System.Data.dll!System.Data.SqlClient.SqlInternalConnectionTds.OpenLoginEnlist(System.Data.ProviderBase.TimeoutTimer timeout, System.Data.SqlClient.SqlConnectionString connectionOptions, System.Data.SqlClient.SqlCredential credential, string newPassword, System.Security.SecureString newSecurePassword, bool redirectedUserInstance) Unknown
    System.Data.dll!System.Data.SqlClient.SqlInternalConnectionTds.SqlInternalConnectionTds(System.Data.ProviderBase.DbConnectionPoolIdentity identity, System.Data.SqlClient.SqlConnectionString connectionOptions, System.Data.SqlClient.SqlCredential credential, object providerInfo, string newPassword, System.Security.SecureString newSecurePassword, bool redirectedUserInstance, System.Data.SqlClient.SqlConnectionString userConnectionOptions, System.Data.SqlClient.SessionData reconnectSessionData, System.Data.ProviderBase.DbConnectionPool pool, string accessToken, bool applyTransientFaultHandling, System.Data.SqlClient.SqlAuthenticationProviderManager sqlAuthProviderManager)    Unknown
    System.Data.dll!System.Data.SqlClient.SqlConnectionFactory.CreateConnection(System.Data.Common.DbConnectionOptions options, System.Data.Common.DbConnectionPoolKey poolKey, object poolGroupProviderInfo, System.Data.ProviderBase.DbConnectionPool pool, System.Data.Common.DbConnection owningConnection, System.Data.Common.DbConnectionOptions userOptions) Unknown
    System.Data.dll!System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(System.Data.ProviderBase.DbConnectionPool pool, System.Data.Common.DbConnection owningObject, System.Data.Common.DbConnectionOptions options, System.Data.Common.DbConnectionPoolKey poolKey, System.Data.Common.DbConnectionOptions userOptions)   Unknown
    System.Data.dll!System.Data.ProviderBase.DbConnectionPool.CreateObject(System.Data.Common.DbConnection owningObject, System.Data.Common.DbConnectionOptions userOptions, System.Data.ProviderBase.DbConnectionInternal oldConnection)   Unknown
    System.Data.dll!System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(System.Data.Common.DbConnection owningObject, System.Data.Common.DbConnectionOptions userOptions, System.Data.ProviderBase.DbConnectionInternal oldConnection)  Unknown
    System.Data.dll!System.Data.ProviderBase.DbConnectionPool.TryGetConnection(System.Data.Common.DbConnection owningObject, uint waitForMultipleObjectsTimeout, bool allowCreate, bool onlyOneCheckConnection, System.Data.Common.DbConnectionOptions userOptions, out System.Data.ProviderBase.DbConnectionInternal connection)   Unknown
    System.Data.dll!System.Data.ProviderBase.DbConnectionPool.TryGetConnection(System.Data.Common.DbConnection owningObject, System.Threading.Tasks.TaskCompletionSource<System.Data.ProviderBase.DbConnectionInternal> retry, System.Data.Common.DbConnectionOptions userOptions, out System.Data.ProviderBase.DbConnectionInternal connection)    Unknown
    System.Data.dll!System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(System.Data.Common.DbConnection owningConnection, System.Threading.Tasks.TaskCompletionSource<System.Data.ProviderBase.DbConnectionInternal> retry, System.Data.Common.DbConnectionOptions userOptions, System.Data.ProviderBase.DbConnectionInternal oldConnection, out System.Data.ProviderBase.DbConnectionInternal connection)    Unknown
    System.Data.dll!System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(System.Data.Common.DbConnection outerConnection, System.Data.ProviderBase.DbConnectionFactory connectionFactory, System.Threading.Tasks.TaskCompletionSource<System.Data.ProviderBase.DbConnectionInternal> retry, System.Data.Common.DbConnectionOptions userOptions)  Unknown
    System.Data.dll!System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(System.Data.Common.DbConnection outerConnection, System.Data.ProviderBase.DbConnectionFactory connectionFactory, System.Threading.Tasks.TaskCompletionSource<System.Data.ProviderBase.DbConnectionInternal> retry, System.Data.Common.DbConnectionOptions userOptions)    Unknown
    System.Data.dll!System.Data.SqlClient.SqlConnection.TryOpenInner(System.Threading.Tasks.TaskCompletionSource<System.Data.ProviderBase.DbConnectionInternal> retry)  Unknown
    System.Data.dll!System.Data.SqlClient.SqlConnection.TryOpen(System.Threading.Tasks.TaskCompletionSource<System.Data.ProviderBase.DbConnectionInternal> retry)   Unknown
    System.Data.dll!System.Data.SqlClient.SqlConnection.OpenAsync(System.Threading.CancellationToken cancellationToken) Unknown
    System.Data.dll!System.Data.Common.DbConnection.OpenAsync() Unknown
>   TestWpf.exe!TestWpf.UndoManagerTest.UndoManagerTestView.Button_Click(object sender, System.Windows.RoutedEventArgs e) Line 35   C#
    PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) Unknown
    PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised)    Unknown
    PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args)   Unknown
    PresentationCore.dll!System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs e)  Unknown
    PresentationFramework.dll!System.Windows.Controls.Primitives.ButtonBase.OnClick()   Unknown
    PresentationFramework.dll!System.Windows.Controls.Button.OnClick()  Unknown
    PresentationFramework.dll!System.Windows.Controls.Primitives.ButtonBase.OnMouseLeftButtonUp(System.Windows.Input.MouseButtonEventArgs e)    Unknown
    PresentationCore.dll!System.Windows.UIElement.OnMouseLeftButtonUpThunk(object sender, System.Windows.Input.MouseButtonEventArgs e)  Unknown
    PresentationCore.dll!System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(System.Delegate genericHandler, object genericTarget) Unknown
    PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target)   Unknown
    PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) Unknown
    PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised)    Unknown
    PresentationCore.dll!System.Windows.UIElement.ReRaiseEventAs(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args, System.Windows.RoutedEvent newEvent)  Unknown
    PresentationCore.dll!System.Windows.UIElement.OnMouseUpThunk(object sender, System.Windows.Input.MouseButtonEventArgs e)    Unknown
    PresentationCore.dll!System.Windows.Input.MouseButtonEventArgs.InvokeEventHandler(System.Delegate genericHandler, object genericTarget) Unknown
    PresentationCore.dll!System.Windows.RoutedEventArgs.InvokeHandler(System.Delegate handler, object target)   Unknown
    PresentationCore.dll!System.Windows.RoutedEventHandlerInfo.InvokeHandler(object target, System.Windows.RoutedEventArgs routedEventArgs) Unknown
    PresentationCore.dll!System.Windows.EventRoute.InvokeHandlersImpl(object source, System.Windows.RoutedEventArgs args, bool reRaised)    Unknown
    PresentationCore.dll!System.Windows.UIElement.RaiseEventImpl(System.Windows.DependencyObject sender, System.Windows.RoutedEventArgs args)   Unknown
    PresentationCore.dll!System.Windows.UIElement.RaiseTrustedEvent(System.Windows.RoutedEventArgs args)    Unknown
    PresentationCore.dll!System.Windows.UIElement.RaiseEvent(System.Windows.RoutedEventArgs args, bool trusted) Unknown
    PresentationCore.dll!System.Windows.Input.InputManager.ProcessStagingArea() Unknown
    PresentationCore.dll!System.Windows.Input.InputManager.ProcessInput(System.Windows.Input.InputEventArgs input)  Unknown
    PresentationCore.dll!System.Windows.Input.InputProviderSite.ReportInput(System.Windows.Input.InputReport inputReport)   Unknown
    PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.ReportInput(System.IntPtr hwnd, System.Windows.Input.InputMode mode, int timestamp, System.Windows.Input.RawMouseActions actions, int x, int y, int wheel)   Unknown
    PresentationCore.dll!System.Windows.Interop.HwndMouseInputProvider.FilterMessage(System.IntPtr hwnd, MS.Internal.Interop.WindowMessage msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled)   Unknown
    PresentationCore.dll!System.Windows.Interop.HwndSource.InputFilterMessage(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled)    Unknown
    WindowsBase.dll!MS.Win32.HwndWrapper.WndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam, ref bool handled) Unknown
    WindowsBase.dll!MS.Win32.HwndSubclass.DispatcherCallbackOperation(object o) Unknown
    WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.InternalRealCall(System.Delegate callback, object args, int numArgs)  Unknown
    WindowsBase.dll!System.Windows.Threading.ExceptionWrapper.TryCatchWhen(object source, System.Delegate callback, object args, int numArgs, System.Delegate catchHandler) Unknown
    WindowsBase.dll!System.Windows.Threading.Dispatcher.LegacyInvokeImpl(System.Windows.Threading.DispatcherPriority priority, System.TimeSpan timeout, System.Delegate method, object args, int numArgs)   Unknown
    WindowsBase.dll!MS.Win32.HwndSubclass.SubclassWndProc(System.IntPtr hwnd, int msg, System.IntPtr wParam, System.IntPtr lParam)  Unknown
    [Native to Managed Transition]  
    [Managed to Native Transition]  
    WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrameImpl(System.Windows.Threading.DispatcherFrame frame)   Unknown
    WindowsBase.dll!System.Windows.Threading.Dispatcher.PushFrame(System.Windows.Threading.DispatcherFrame frame)   Unknown
    PresentationFramework.dll!System.Windows.Application.RunDispatcher(object ignore)   Unknown
    PresentationFramework.dll!System.Windows.Application.RunInternal(System.Windows.Window window)  Unknown
    PresentationFramework.dll!System.Windows.Application.Run(System.Windows.Window window)  Unknown
    PresentationFramework.dll!System.Windows.Application.Run()  Unknown
    TestWpf.exe!TestWpf.App.Main() Line 53  C#

person David Skula    schedule 03.08.2019    source источник
comment
Сколько времени он остается заблокированным?   -  person Seyedraouf Modarresi    schedule 03.08.2019
comment
Приостановите отладчик во время зависания. Опубликуйте трассировку стека потока пользовательского интерфейса, включая внешний код.   -  person usr    schedule 03.08.2019
comment
Он остается заблокированным до истечения времени соединения.   -  person David Skula    schedule 03.08.2019


Ответы (2)


Краткая версия:

Код подключения, похоже, находится в синхронной части файла OpenAsync.

В таком случае кажется, что есть единственный вариант - рассматривать OpenAsync как в основном синхронный метод и выполнять его с Task.Run, чтобы задержка происходила в потоке пула потоков.

Task.Run(async () => ... await connection.OpenAsync() ...);

TLDR:

Скорее всего, проблема связана с текущей реализацией OpenAsync - https://github.com/microsoft/referencesource/blob/master/System.Data/System/Data/SqlClient/SqlConnection.cs#L1383

На первый взгляд не уверен, но пока кажется, что слишком мало ConfigureAwait(false) в OpenAsync и других вызываемых им методах.

Это означает, что, вероятно, код, который ищет хост БД и повторяет попытку подключения к отключенной службе, просто выполняется в текущем (графическом интерфейсе) потоке, на самом деле не допуская никаких "async voidness", которые можно ожидать.

В то время как, когда вы вызываете его во второй раз, кэширование работает так, что возвращается почти мгновенно.

Итак, я рекомендую вам попробовать добавить ConfigureAwait(false) в обработчик событий.

using (var conn = new SqlConnection("...."))   
{
    await conn.OpenAsync().ConfigureAwait(false);
}

Поскольку ConfigureAwait кажется, не помогает, то это, вероятно, означает, что исходный код подключения находится в синхронной части OpenAsync.

В таком случае кажется, что есть единственный вариант — рассматривать OpenAsync как в основном синхронный метод и выполнять его с Task.Run, чтобы задержка происходила в потоке пула потоков:

private async void Button_Click(object sender, RoutedEventArgs e)
{
    await Task.Run(async () =>
        {
            try
            {
                using (var conn = new SqlConnection("...."))
                {
                    await conn.OpenAsync();
                }
            }
            catch (System.Exception)
            {
                MessageBox.Show("err");
                return;
            }
            MessageBox.Show("ok");
        }
    );
}
person Eugene Podskal    schedule 03.08.2019
comment
Спасибо за ответ. К сожалению, ConfigureAwait(false) не работает. - person David Skula; 03.08.2019
comment
Использование Task.Run() кажется единственным вариантом... Но это всего лишь обходной путь, я считаю, что это ошибка в реализации OpenAsync, и удивляюсь, почему она еще не обнаружена и не исправлена. - person David Skula; 03.08.2019
comment
@DavidSkula Похоже, так оно и есть. Вы можете создать задачу на github.com/dotnet/corefx для этой проблемы — github.com/dotnet/corefx/ . - person Eugene Podskal; 03.08.2019

Трассировка стека показывает, что ADO.NET выполняет синхронный ввод-вывод, хотя вы запросили асинхронный ввод-вывод. Это ошибка в ADO.NET, которая, вероятно, возникает в каком-то довольно редком пути кода, поэтому ее не заметили.

ADO.NET был обновлен относительно недавно и поэтапно, чтобы лучше поддерживать асинхронный ввод-вывод. Асинхронный доступ к базе данных был редкостью на заре .NET.

Вы можете попробовать перейти на более новую версию .NET.

Сообщите об этой ошибке в репозитории corefx на GitHub.

Вы можете обойти это, запустив операцию блокировки в потоке, отличном от пользовательского интерфейса:

await Task.Run(async () => await conn.OpenAsync());
person usr    schedule 03.08.2019