views:

548

answers:

1

I'm developing a thread safe class and I want to wrap async method call around a sync method call. Maybe I'm not looking for a definition of "asynccall" method, but something close to it. When calling AsyncToStartSearchPage() on the instance, I want the calling thread to block until the new thread obtains the lock on the object. That's where the semaphore comes in. The asyncCalls lock is to restrict the use. Maybe it isn't the best implementation, but I feel it needs some way of restricting the number of async requests at a time. Any feed back would be great. The application use is that I have a consumer/producer model where a thread calling this method would begin the ToStartPage() method call and place it into the buffer. On the other side I want a thread to take the object with no possibilty of changing the state of the object before ToStartPage() has finished. Before this, I had issues where the consumer would obtain lock on the object before the spun of thread was able to obtain lock and perform the ToStartPage().

    private readonly Object obj_Lock = new Object();
    private readonly Object asyncCalls = new Object();
    private Semaphore asyncSema = new Semaphore(0, 1);

    //sync method call which has a async calling wrapper
    public void ToSearchPage()
    {
        lock (obj_Lock)
        {

             //do something
        }
    }

    //public async method wrapper
    public bool AsyncToStartSearchPage()
    {
        //used to limit one async call.
        //not crazy about this part, but I feel it needs something like this
        if (Monitor.TryEnter(asyncCalls, 1))
        {
            try
            {
                Thread t = new Thread(asyncToStartPage);
                t.Start();

                //  blocks until the new thread obtains lock on object
                asyncSema.WaitOne();
            }
            finally
            {
                Monitor.Exit(asyncCalls);

            }
                return true;
        }
        else
        {
            return false;
        }

    }



    private void asyncToStartPage()
    {
        lock (obj_Lock)
        {
            //  now that I have aquired lock, release calling thread
            asyncSema.Release();
            ToSearchPage();
        }
    }
+1  A: 

A cleaner alternative might look like the following. You do not need a semaphore to get the calling thread to wait until the lock is acquired. Notice how I have used a ManualResetEvent and made a redundant lock construct for that purpose. Once the outer lock is acquired the event is signalled and since locks are reentrant the inner lock will be acquired immediately. Now, I have used a semaphore to throttle the execution of AsyncToSearchPage.

public class YourThreadSafeClass
{
  private Object m_Lock = new Object();
  private Semaphore m_Semaphore = new Semaphore(1, 1);

  public void ToSearchPage()
  {
    lock (m_Lock)
    {
       // Actual work goes here.
    }
  }

  public bool AsyncToSearchPage()
  {
    // Throttle access using a semaphore
    if (m_Semaphore.WaitOne(0)) 
    {
      try 
      { 
        ManualResetEvent e = new ManualResetEvent(false);
        Thread t = new Thread(
            () =>
            {
              // Acquire the lock here for the purpose of signalling the event
              lock (m_Lock)
              {
                e.Set();
                ToSearchPage(); // The lock will be acquired again here...thats ok
              }
            }
          );
        t.Start();
        e.WaitOne(); // Wait for the lock to be acquired
        return true;
      }
      finally 
      {
        // Allow another thread to execute this method
        m_semaphore.Release();
      } 
    }
    return false;
  }
}

Keep in mind that this a slightly cleaner way of doing the same general thing. However, I recommend avoiding this approach altogether as it is still ugly. I am not really understanding the need to throttle access to the asynchronous method nor have it block until the wrapped synchronous method acquires its lock anyway. Instead, consider the BeginXXX/EndXXX asynchronous pattern that is used throughout the .NET Framework.

Brian Gideon