2

I'm working on a .NET application where I off-load my DB work (save operations) to a separate thread (inspired by how NodeJS handles IO operations). A save operation comes down to this (using Entity Framework):

public void Save(Customer customer)
{
    Task.Factory.StartNew(() =>
    {
        using (var entities = new MyEntities())
        {
            var persistedCustomer 
                = entities.Customers.FirstOrDefault(x => x.Number == customer.Number);

            if (persistedCustomer == null)
            {
                persistedCustomer = new DBCustomer();
                // set properties
                entities.Customers.AddObject(persistedCustomer);
            }
            else
            {
                // set properties
            }

            entities.SaveChanges();
        }
    });
}

So, in essence:

  • check if entity already exists in the DB
  • if not, create a new one
  • if it does already exist, update the existing one

Where this sometimes breaks down is when two calls to the Save method are made at the same time. Both threads see the entity doesn't exist yet and they want to do an insert. A standard race condition.

How would you go about fixing this? Any experience with this?

I could implement a retry mechanism, but was wondering if there are other options. A simple retry could of course overwrite certain properties that the previous save had updated.

  • One problem with your example is that this code assumes Customer (the one passed into your Save function) is a thread safe object. Either you need to make Customer immutable, or you need to use some other kind of locking or memory barrier. I suggest creating an immutable copy. – Scott Whitlock Mar 17 '15 at 15:46
  • 4
    ...since Entities are by definition mutable objects, you should never ever pass an Entity across a thread boundary, which I'm afraid is what you're doing here. – Scott Whitlock Mar 17 '15 at 15:53
6

Problems like this are often solved with queues.

If you are saving same customer, you insert it into one ConcurrentQueue<T>. Then there is one thread that reads the queue sequentially and does the update on each item read. Only place where the synchronization between threads occurs is in insertion to the queue and that can be done both fast and without risk of deadlocks.

  • I use producer consumer queues quite a bit in my multithreaded code. System.Collections.Concurrent is a wonderful namespace added in to the framework. – RubberChickenLeader Mar 17 '15 at 20:31

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.