0

In my application I use a ReaderWriterLockSlim to synchronize reads and writes from/to a list<>.

In the following example reading the list is performed inside all 3 sub-methods, thus these 3 should be packed into a ReadLock. The problem is that SubMethod3 is called via a BackgroundWorker (as it contains lengthy calculations), so the ExitReadLock() in the finally block of MainMethod1 might be called before SubMethod3 has been finished by the BackgroundWorker (separate thread). Thereby the code in SubMethod3 is not really protected by the lock.

What I have considered is to use a lock in each sub-method, so Submethod3 would have its own lock, which would be released when the BackgroundWorker was done. The problem with this approach is that another thread could enter in between the calls of the sub-methods, as each of these would release the lock when done.

My question is: How can ReadLock be used to protect over more threads?

ReaderWriterLockSlim synchronizationLock = new ReaderWriterLockSlim();

public void MainMethod1()
    {
        synchronizationLock.EnterReadLock();
        try
        {
            SubMethod1(); //Run on UI thread
            SubMethod2(); //Run on UI thread
            myBackgroundWorker.RunWorkerAsync();  
        }
        finally
        {
            synchronizationLock.ExitReadLock();
        }
    }


private void myBackgroundWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            SubMethod3(); //Run on separate thread
        }
halfer
  • 19,824
  • 17
  • 99
  • 186
JohnSaps
  • 602
  • 5
  • 16
  • Hmm, no, you are calling SubMethod3 from MainMethod1(). If you *also* call it from BGW then it must use *synchronizationLock* all by itself. – Hans Passant Sep 22 '15 at 16:50
  • OK, I updated the question to make it more clear what I am already doing. – JohnSaps Sep 22 '15 at 17:04
  • @ downvoter: why? Is the question not clear, or am I completely off? Without a comment no one gets wiser. – JohnSaps Sep 22 '15 at 17:30
  • (It's fine to ask about downvotes if the comments, though in my experience the voter will already have moved on, so there's not a lot of value in doing so. We do ask however that such remarks are not added into posts, since they are not of interest to the vast majority of future readers, and people will sometimes downvote if you put them in anyway). – halfer Sep 22 '15 at 19:01
  • @halfer: OK, thanks. Are you able to explain to me why the question gets downvoted, even after you've edited it? Without an explanation I do not know what to change, or how to describe such questions in the future. – JohnSaps Sep 23 '15 at 10:32
  • No; voting is anonymous, and people mostly don't explain their downvotes. I think it would be helpful if they did, but there is no requirement to do so. If you are keen to earn points, answering a question every now and again will help you earn more points than you lose. Long story short: don't worry about it `:-)`. – halfer Sep 23 '15 at 12:01

1 Answers1

2

In general you are out of luck. Due to thread affinity, you cannot release the lock from another thread. If you try to hold the reader lock (with the idea that this will allow the worker to acquire its own read lock), and wait the worker thread to start, acquire a reader lock and notify you, so you can release the read lock at that time, all you'll get would be a deadlock if there is a waiting writer thread due to reader/waiter fairness of the ReaderWriterLock(Slim) implementation.

I see the following options:

(A) Run the whole MainMethod1 on a separate thread.

(B) Write and use your own reader/writer lock implementation that supports such scenario.

(C) Get rid of BackgroundWorker and switch to async/await implementation using one of the AsyncReaderWriterLock implementations described here ReaderWriterLockSlim and async\await

(D) Since I've noticed the comment Run on UI thread, only and only if the method is used by a thread that supports marshalling calls from another thread (which is true for WF and WPF UI threads), you can use the following technique:

public void MainMethod1()
{
    synchronizationLock.EnterReadLock();
    bool releaseLock = true;
    try
    {
        SubMethod1();
        SubMethod2();
        RunWorkerCompletedEventHandler onRunWorkerCompleted = null;
        onRunWorkerCompleted = (sender, e) =>
        {
            ((BackgroundWorker)sender).RunWorkerCompleted -= onRunWorkerCompleted;
            synchronizationLock.ExitReadLock();
        };
        myBackgroundWorker.RunWorkerCompleted += onRunWorkerCompleted;
        myBackgroundWorker.RunWorkerAsync();
        releaseLock = false;
    }
    finally
    {
        if (releaseLock)
            synchronizationLock.ExitReadLock();
    }
}

Please note than while option (D) seems to solve the issue in the context of MainMethod1, it can easily lead to a deadlock if the UI thread tries to acquire read lock from another place and there is a pending writer waiting.

In general working with long time hold locks from the UI thread is a bad idea that is causing more problems rather than solving something. IMO the best are the options (A) and (C), and so far (A) is the simplest if you can afford it (if SubMethod1 and SubMethod2 do not need to be run on the UI thread or can marshall the necessary calls to it).

Community
  • 1
  • 1
Ivan Stoev
  • 195,425
  • 15
  • 312
  • 343
  • Thanks for the answer! It is late here in Europe, so I'll look closer into it tomorrow morning. – JohnSaps Sep 22 '15 at 18:14
  • John, I'm going to update it, please don't review it before that. – Ivan Stoev Sep 23 '15 at 10:30
  • I ended up rewriting my program in different ways, whereby this is not an issue anymore. I did not manage to apply any of your suggestions to my case, but thanks a lot anyway! – JohnSaps Feb 21 '16 at 18:15