BeginInvoke() occasionally breaks (System.InvalidOperationException) with Invalid attempt to call Read when reader is closed

StackOverflow https://stackoverflow.com/questions/13094682

Domanda

In following code snippet, AddRow() is called from a non-UI thread:

  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();

    }
}

Before having to call AddRow() from a non-UI thread, I had UpdateGridView() being called directly and it worked flawlessly. But now AddRow() is no longer guaranteed to be called from a UI thread and so I replaced the direct call with dataGridView1.BeginInvoke().

As soon as I did that, my form-based application started throwing a System.InvalidOperationException every several AddRow() calls, breaking on the mSqlDataAdapter.Fill(mDataSet); statement (!) with the following message:

Invalid attempt to call Read when reader is closed

My question is why?

  1. What reader? the DataAdapter's? The SqlConnection's? The DataGridView's data source?
  2. I am taking care of surrounding BeginInvoke() with mSqlConnection's Open() and Close() and I even open mSqlConnection (again!) if it isn't open, so how come I'm receiving this "closed" error?
  3. What is the correct way to solve this problem? (i.e. updating DataGridView from a non-UI thread)
È stato utile?

Soluzione

The issue is certainly due to the race condition.

Remove these two lines from UpdateGridView,as it is not the right place to close your connection.

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

use IAsyncResult to retrieve a waithandle and wait for the thread to complete the GridUpdate.

 IAsyncResult Result = dataGridView1.BeginInvoke(new InvokeDelegate(UpdateGridView));
 Result.AsyncWaitHandle.WaitOne();
 mSqlConnection.Close();

Altri suggerimenti

I think that you have a race issue, look this part of the code in your thread:

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

BeginInvoke will run delegate when such thing is convenient for the control. So you call it from the thread and it starts your Fill in the UI thread which can last long (You are getting evrything from the table) and while it is fetching the data, call to closing the connection in the separate thread closes it. You mentioned that it happens only sometimes, well in most cases, thread will close the connection and you will reopen it in the UpdateGridView() method, but sometimes, you will start filling the dataset before the thread closes the connection. You should synchronize opening and closing of the connection.

Switching these two should solve your problem but you will make two connections each time:

mConnection.Close();
dataGridView1.BeginInvoke(new InvokeDelegate(UpdateGridView));
Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top