Search code examples
c#vb.netmultithreadingoledbcommandoledbdataadapter

Can I refrain from calling .Dispose on an OleDBCommand or an OleDbDataAdapter when it's used in a Thread?


We have had some issues with RaceOnRCWCleanups when we decided to use Threads to fill certain data sets in the background to make our application more responsive from the End User's perspective.

After some investigation we discovered that this happens when we fill a DataSet using an OleDbCommand and an OleDbDataAdapter, and when we call this from a background Thread.

When we call it from the main UI Thread there is no problem. If anything fails while we're trying to fill the DataSet, we just make sure all these objects are tidied up, disposed and set to null before we exit from the procedure that fills the DataSet.

But when we call the same procedures from a Thread we find we sometimes cause RaceOnRCWCleanup issues.

I can make the problem "go away" by simply checking

System.Threading.Thread.CurrentThread.IsBackground

If this is false I will simply not call .Dispose on the OleDbDataAdapter and on the OleDbCommand. I will just set them to null and leave the rest to the Garbage Collector. When I do this I no longer get RaceOnRCWCleanup errors.

The problem is: Am I setting myself up for failure down the line here? I cannot give you very specific examples of this but I just want some general advice.

Even if the Garbage Collector will take its sweet time and it can be a long while before it eventually clears away the objects I don't dispose, THAT is something I can live with. If, however, these will now hang around indefinitely and they will never get cleared away, then I may be setting myself up for new problems down the line.

So I really need to hear from someone with expertise on OleDbDataAdapter and OleDbCommand objects and how .NET Framework deals with these if they aren't explicitly disposed.

Many thanks.


Solution

  • You need to dispose objects all disposable objects†, regardless if running on a background thread or not. You also need to ensure you are not using any objects in a non-thread safe way. In the OleDb case it will be safest to create the connection, command, and adapter all on the same thread.

    Another thing to keep in mind is that objects should be disposed in the inverse order they where created in, and disposed even if an exception occur. This is easiest done with the using statement:

    public void ReadMyData(string connectionString)
    {
        string queryString = "SELECT OrderID, CustomerID FROM Orders";
        using var connection = new OleDbConnection(connectionString);
        using var command = new OleDbCommand(queryString, connection);
        connection.Open();
        using var reader = command.ExecuteReader();
    
       while (reader.Read())
       {
           Console.WriteLine(reader.GetInt32(0) + ", " + reader.GetString(1));
       }
    }
    

    Adapted from MS OleDbCommand example. This should ensure that each object is guaranteed to be disposed, and disposed in the correct order, regardless if any operation fail or not. If you have any custom classes that owns a IDisposable object, you need to make that class IDisposable.

    † There are some execptions to the "always dispose"-rule, notable examples being MemoryStream and Task, disposing these does not hurt, but I would not put much effort into ensuring it is done.