BeginInvoke() иногда прерывается (System.InvalidOperationException) с недопустимой попыткой вызвать Read, когда программа чтения закрыта

В следующем фрагменте кода AddRow() вызывается из потока, отличного от пользовательского интерфейса:

  public partial class Form1 : Form
  {
    public delegate void InvokeDelegate();

    ...
    SqlConnection mSqlConnection = new SqlConnection("Data Source=" + Environment.MachineName + "\\SQLEXPRESS; Initial Catalog=orderDB; Integrated Security=TRUE; MultipleActiveResultSets=True;");

    DataSet mDataSet = new DataSet();
    SqlDataAdapter mSqlDataAdapter = new SqlDataAdapter();


    ...
    private void UpdateGridView()
    {
      if (mSqlConnection.State == ConnectionState.Closed)
        mSqlConnection.Open();

      mSqlDataAdapter.SelectCommand = new SqlCommand("SELECT * FROM customerTable", mSqlConnection);
      mDataSet.Clear();
      mSqlDataAdapter.Fill(mDataSet);
      dataGridView1.DataSource = mDataSet.Tables[0];

      if (mSqlConnection.State == ConnectionState.Open)
        mSqlConnection.Close();
    }


    public void AddRow(int field1, int field2, int field3)
    {
      mSqlDataAdapter.InsertCommand = new SqlCommand("INSERT INTO customerTable VALUES(@field1, @field2, @field3)", mSqlConnection);

      mSqlDataAdapter.InsertCommand.Parameters.Add("@field1", SqlDbType.Int).Value = field1;
      mSqlDataAdapter.InsertCommand.Parameters.Add("@field2", SqlDbType.Int).Value = field2;
      mSqlDataAdapter.InsertCommand.Parameters.Add("@field3", SqlDbType.Int).Value = field3;

      mSqlConnection.Open();
      mSqlDataAdapter.InsertCommand.ExecuteNonQuery();
      dataGridView1.BeginInvoke(new InvokeDelegate(UpdateGridView)); // UpdateGridView() won't work from a non-UI thread
      mSqlConnection.Close();

    }
}

Прежде чем мне пришлось вызывать AddRow() из потока, отличного от пользовательского интерфейса, у меня был прямой вызов UpdateGridView(), и он работал безупречно. Но теперь AddRow() больше не гарантируется вызов из потока пользовательского интерфейса, поэтому я заменил прямой вызов на dataGridView1.BeginInvoke().

Как только я это сделал, мое приложение на основе формы начало выдавать System.InvalidOperationException каждые несколько вызовов AddRow(), прерывая оператор mSqlDataAdapter.Fill(mDataSet); (!) со следующим сообщением:

Неверная попытка вызвать Read, когда ридер закрыт

Мой вопрос: почему?

  1. Какой читатель? адаптер данных? SqlConnection? Источник данных DataGridView?
  2. Я забочусь об окружении BeginInvoke() с помощью Open() и Close() mSqlConnection, и я даже открываю mSqlConnection (снова!), если он не открыт, так почему же я получаю эту «закрытую» ошибку?
  3. Как правильно решить эту проблему? (т. е. обновление DataGridView из потока, отличного от пользовательского интерфейса)

person scatmoi    schedule 26.10.2012    source источник


Ответы (2)


Проблема, безусловно, связана с состоянием гонки.

Удалите эти две строки из UpdateGridView, так как это неподходящее место для закрытия соединения.

 if (mSqlConnection.State == ConnectionState.Open)
    mSqlConnection.Close();

используйте IAsyncResult, чтобы получить дескриптор ожидания и дождаться завершения потока. Обновление сетки.

 IAsyncResult Result = dataGridView1.BeginInvoke(new InvokeDelegate(UpdateGridView));
 Result.AsyncWaitHandle.WaitOne();
 mSqlConnection.Close();
person Prabhu Murthy    schedule 26.10.2012
comment
Отлично, я не знал, что это существует +1. - person Nikola Davidovic; 27.10.2012
comment
Работает отлично! Но разве это не то же самое, что и вызов синхронного dataGridView1.Invoke()? :) - person scatmoi; 27.10.2012

Я думаю, что у вас проблема с гонкой, посмотрите эту часть кода в своей ветке:

 dataGridView1.BeginInvoke(new InvokeDelegate(UpdateGridView)); 
 mConnection.Close();

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

Переключение этих двух должно решить вашу проблему, но каждый раз вы будете выполнять два подключения:

mConnection.Close();
dataGridView1.BeginInvoke(new InvokeDelegate(UpdateGridView));
person Nikola Davidovic    schedule 26.10.2012
comment
Я думаю, вы правы. Но как решить эту проблему? Как синхронизировать открытие и закрытие соединения? - person scatmoi; 27.10.2012
comment
@scatmoi Проверьте редактирование, оно решит проблему на данный момент, но я настоятельно рекомендую проверить некоторые механизмы синхронизации. - person Nikola Davidovic; 27.10.2012
comment
Я просто сделал именно так, как вы посоветовали, и проблема все еще сохраняется. Я не так удивлен, потому что ошибка жалуется на то, что что-то закрыто, а не открыто. Кстати, он всегда ломается на операторе mSqlDataAdapter.Fill(mDataSet);. - person scatmoi; 27.10.2012
comment
@scatmoi Отличаются ли mSqlConnection и mConnection? - person Nikola Davidovic; 27.10.2012
comment
@scatmoi Удалите if (mSqlConnection.State == ConnectionState.Closed), поскольку вы только что закрыли соединение, оно, вероятно, находится в каком-то другом состоянии, кроме Closed, оставьте только mSqlConnection.Open - person Nikola Davidovic; 27.10.2012
comment
Нет, удаление if (mSqlConnection.State == ConnectionState.Closed) не решило проблему. Опять же, я и здесь не так уж удивлен, потому что ошибка жалуется на то, что что-то закрывается, а не открывается. - person scatmoi; 27.10.2012
comment
@scatmoi Проверьте другой ответ, вот и все. Я не мог найти лучший метод синхронизации. Что CodeIgnoto — очень хорошее решение для вашего кода. - person Nikola Davidovic; 27.10.2012