none
Асинхронное подключение к базе (независимое выполнение запросов в различных потоках) RRS feed

  • Вопрос

  • Доброго времени суток,

    возник вопрос с которым маюсь уже неделю (если не больше).
    Предыстория:
    В программе имеется статический класс, занимающийся соединениями с базой данных (он содержит все необходимые функции)

    public static class SqlWorker
    {
        private static SqlConnection Conn{get;set;}
    
        //GetDataSet
        //GetTable
        //ExecuteQuery
        //GetTableAsync
    
    }
    

    так уж сложилось, что этот класс используется везде и он сам разбирается к какому серверу он подключается и т.п.
    Подключение к базе данный создается при первом обращении к одной из функций класса и сохраняется до конца работы программы.
    Все функции, кроме GetTableAsync работают в синхронном режиме (поток ожидает их выполнения).
    Далее пытаемся разобраться с функцией асинхронного получения таблицы:

    private static SqlCommand InitNewCommandWithoutConnection(string sQuery, SqlParameter[] param)
        {
          SqlCommand comm = new SqlCommand(sQuery);
    
          if (param != null)
          {
            comm.Parameters.AddRange(param);
          }
          return comm;
        }
    
        private static void GetTableAsyncThreadFunc(object Param)
        {
          StateObject so = Param as StateObject;
          // если вдруг нам подсунули что-то чужое
          if (so == null)
            return;
          SqlDataReader reader = null;
          SqlConnectionStringBuilder sb = new SqlConnectionStringBuilder(Conn.ConnectionString);
          SqlConnection conn = new SqlConnection(sb.ConnectionString);
          try
          {
            so.Command.Connection = conn;
            conn.Open();
    
            reader = so.Command.ExecuteReader();
            DataTable table = new DataTable();
            try
            {
              table.Load(reader);
            }
            finally
            {
              reader.Close();
            }
    
            InvokeResultTable(so, table);
          }
          catch (Exception ex)
          {
            Forms.ErrorMessageBox.Show(ex);
          }
          finally
          {
    
            conn.Close();
          }
        }
    
    
        private static void GetTableAsync(string sQuery, GetTableAsyncDelegate getTableFunc, SqlParameter[] param)
        {
          //SqlCommand comm = InitNewCommand(sQuery, param);
          //comm.BeginExecuteReader(
          //  new AsyncCallback(HandleCallback),
          //  new StateObject(getTableFunc, comm),
          //  CommandBehavior.CloseConnection);
         
    
          Thread thread = new Thread(GetTableAsyncThreadFunc);
          thread.Start(new StateObject(getTableFunc, InitNewCommandWithoutConnection(sQuery, param)));
        }
        private class StateObject
        {
          public GetTableAsyncDelegate GetTableFunc;
          public GetTableAsyncDelegateParam GetTableFuncParam;
          public object Param;
          public SqlCommand Command;
    
          public bool IsDelegateWithParameters { get; private set; }
          public StateObject(){}
          public StateObject(GetTableAsyncDelegate getTableFunc, SqlCommand command = null)
          {
            GetTableFunc = getTableFunc;
            Command = command;
            IsDelegateWithParameters = false;
          }
          public StateObject(GetTableAsyncDelegateParam getTableFuncParam, object param, SqlCommand command = null)
          {
            GetTableFuncParam = getTableFuncParam;
            Command = command;
            Param = param;
            IsDelegateWithParameters = true;
          }
        }
    



    суть функции в том, что она создает подключение новое(!) с той же ConnectionString, что и у основного подключения. т.е. теоретически если эту функцию вызовут 1 раз - суммарное количество подключений будет 2, если 2 раза - то 3. и запросы, выполняемые в этих подключениях никак друг на друга не влияют.
    Однако, периодически вылетает исключение:
    [quote]Существует назначенный этой команде открытый DataReader, который требуется предварительно закрыть.[/quote]
    Самое интересное, что это исключение происходит у основного потока (основного подключения, которое висит все время выполнения программы)

    почему такая вещь может возникать, и почему получается так, что создается второе подключение и оно висит параллельно первому (хотя я явно его закрываю в программе). Это подключение отпадает по истечении определенного времени (для меня пока не понятно какого) а когда выполняется функция повторно - она не создает 3е подключение, а (как я понял) использует второе, которое после себя оставил прошлый запуск этой функции.

    Вопросы:
    1) что делать чтобы избавиться от этого исключения в основном потоке?
    2) как сделать чтобы было именно полностью асинхронное выполнение запросов (для каждого вызова асинхронного запроса создавалось полноценное новое соединение)?

     


    • Изменено Baxster 27 октября 2011 г. 13:16
    27 октября 2011 г. 13:16

Ответы

  • Вы явно наступаете на какие-то грабли с пулингом, получая из него кривую коннекцию, сперва её туда вернув.  Для начала я бы, как и советует http://msdn.microsoft.com/en-us/library/ms971481.aspx#adonetbest_topic5 , обернул работу с SQLConnection в using.
    • Помечено в качестве ответа Baxster 28 октября 2011 г. 10:34
    27 октября 2011 г. 16:44
  • Решением стало использование using для синхронного метода получения данных и добавление Pooling= false в асинхронную часть запросов, хотя может и не было необходимости в этом.

    Корни зла пошли от незнания того, что майкрософт использует пулы соединений для работы класса SqlConnection

    • Помечено в качестве ответа Baxster 28 октября 2011 г. 10:34
    28 октября 2011 г. 10:33

Все ответы

  • Вы явно наступаете на какие-то грабли с пулингом, получая из него кривую коннекцию, сперва её туда вернув.  Для начала я бы, как и советует http://msdn.microsoft.com/en-us/library/ms971481.aspx#adonetbest_topic5 , обернул работу с SQLConnection в using.
    • Помечено в качестве ответа Baxster 28 октября 2011 г. 10:34
    27 октября 2011 г. 16:44
  • данный модуль служит цели создания единого подключения на все время работы программы. никаким пулом подключений я не управляю и не собираюсь. мне нужно банально держать одно подключение постоянно и использовать второе (третье и т.д.) когда мне это необходимо для асинхронных вызовов. есть ли статьи на русском про это дело?
    28 октября 2011 г. 5:08
  • Еще одно уточнение:

    стал использовать подключения без использования пула (Pooling=false) и делаю эксперимент:

    запуская долгое асинхронное соединение (оно по идее вообще никак не должно влиять на работу основного, т.к. его нет ни в пуле соединений ни еще где либо, для пущей наглядности я даже меняю InitialCatalog на другую базу (для запросов это все равно, т.к. в них глобально прописано имя базы которое задействовано в конкретном запроса)) и после этого делаю несколько запросов в синхронном соединении.

    Первый запрос проходит гладко без вопросов, а вот второй почему-то валится с ошибкой на открытый датаридер, хотя я точно знаю, что он закрыт, ибо аналогичные действия без запущенного фонового (асинхронного) запроса выполняются без вопросов.

    Что происходит - я не могу понять. Все действия в процедурах делаются либо с DataAdapter, который заполняет датасет и закрывается или же напрямую через SqlCommand.ExecuteNonQuery

    Может кто сталкивался с такими вещами? или подскажите, как правильно надо организовывать работу с фоновой загрузкой данный, не влияющей на другую (не фоновую) загрузку? наверняка, ранее такие вопросы возникали.

    первоначальная работа вообще была сделана с использованием BeginDataReader и EndDataReader, но ошибка была аналогичная (не помню с одной или несколькими командами)

    Что делать?

    28 октября 2011 г. 6:15
  • Решением стало использование using для синхронного метода получения данных и добавление Pooling= false в асинхронную часть запросов, хотя может и не было необходимости в этом.

    Корни зла пошли от незнания того, что майкрософт использует пулы соединений для работы класса SqlConnection

    • Помечено в качестве ответа Baxster 28 октября 2011 г. 10:34
    28 октября 2011 г. 10:33
  • Спасибо тебе добрый человек !)

    для тех кто вдруг столкнется как я 

    Pooling 'true'
    Когда значение этого ключа установлено равным true, все вновь созданные подключения добавляются в пул при закрытии их приложением. В следующей попытке открыть то же соединение будет использоваться связи из пула.
    Подключения считаются одинаковыми, если у них одинаковые строки подключения. Разные подключения имеют различные строки подключения.
    Этот ключ может иметь значение "true", "false", "yes" или "no".

    12 июля 2013 г. 5:01