Home > Mobile >  how to execute a block of code atomically in C#
how to execute a block of code atomically in C#

Time:12-24

I have a multi-threaded UI application that starts numerous background threads. A lot of these threads execute code that looks as follows:

public void Update(){
   if(Dispatcher.HasShutdownStarted()) return;
   Dispatcher.Invoke(()=>{...});
...
}

Then I sometimes may have a thread execute the following code

pubic void Shutdown(){
   if(Dispatcher.HasShutdownStarted()) return;
   Dispatcher.InvokeShutdown();
}

The problem is that sometimes one thread executes Dispatcher.InvokeShutdown() AFTER another thread executed Dispatcher.HasShutdwonStarted() but before it got to Dispatcher.Invoke(()=>{...}). Which means, that there will be a thread trying to execute a lambda on the Dispatcher once the Dispatcher has begun to shut down. And that's when I get exceptions. What is the best solution to this?

CodePudding user response:

The problem you face is that the HasShutdownStarted is checked, before the code inside the Invoke is executed (because it's queued on the dispatcher)

I think a better way is to check it inside the invoke, this way you don't need any locks.

public void Update(){
   Dispatcher.Invoke(()=>
   {
       if(Dispatcher.HasShutdownStarted()) return;
       ...
   });
}

CodePudding user response:

One option could be to create a Synchronized block with any object, like:

// Define property
private readonly object _thisLock = new object();

Then use synchronized keyword in the code you want only one thread at the time

    public void SetTps(decimal limit)
    {
        if (_limit == limit || limit <= 0)
            return;
        // This is the important part
        lock (_thisLock)
        {
            _limit = limit;
            ResetTps();
        }
    }    

Locks are a little heavy, you can also try with a semaphore or semaphore slim:

// First you create the semaphore
semaphore = new SemaphoreSlim(0, 3);
// Then you solicit with wait
semaphore.Wait();
// Do all your work that needs only one resource at the time
// After you release the semaaphore
semaphore.Release();

EDIT

Almost forgot, for your case, where there are many reads and not so many writes, you can use ReaderWriterLockSlim

public class SynchronizedCache 
{
    private ReaderWriterLockSlim cacheLock = new ReaderWriterLockSlim();
    private Dictionary<int, string> innerCache = new Dictionary<int, string>();

    public int Count
    { get { return innerCache.Count; } }

    public string Read(int key)
    {
        cacheLock.EnterReadLock();
        try
        {
            return innerCache[key];
        }
        finally
        {
            cacheLock.ExitReadLock();
        }
    }

    public void Add(int key, string value)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Add(key, value);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }

    public bool AddWithTimeout(int key, string value, int timeout)
    {
        if (cacheLock.TryEnterWriteLock(timeout))
        {
            try
            {
                innerCache.Add(key, value);
            }
            finally
            {
                cacheLock.ExitWriteLock();
            }
            return true;
        }
        else
        {
            return false;
        }
    }

    public AddOrUpdateStatus AddOrUpdate(int key, string value)
    {
        cacheLock.EnterUpgradeableReadLock();
        try
        {
            string result = null;
            if (innerCache.TryGetValue(key, out result))
            {
                if (result == value)
                {
                    return AddOrUpdateStatus.Unchanged;
                }
                else
                {
                    cacheLock.EnterWriteLock();
                    try
                    {
                        innerCache[key] = value;
                    }
                    finally
                    {
                        cacheLock.ExitWriteLock();
                    }
                    return AddOrUpdateStatus.Updated;
                }
            }
            else
            {
                cacheLock.EnterWriteLock();
                try
                {
                    innerCache.Add(key, value);
                }
                finally
                {
                    cacheLock.ExitWriteLock();
                }
                return AddOrUpdateStatus.Added;
            }
        }
        finally
        {
            cacheLock.ExitUpgradeableReadLock();
        }
    }

    public void Delete(int key)
    {
        cacheLock.EnterWriteLock();
        try
        {
            innerCache.Remove(key);
        }
        finally
        {
            cacheLock.ExitWriteLock();
        }
    }

    public enum AddOrUpdateStatus
    {
        Added,
        Updated,
        Unchanged
    };

    ~SynchronizedCache()
    {
       if (cacheLock != null) cacheLock.Dispose();
    }
}

For your case, It would look something like:

public void Update(){
   cacheLock.EnterReadLock();
   try {
      if(Dispatcher.HasShutdownStarted()) return;
   } finally {
      cacheLock.ExitReadLock();
   }
   Dispatcher.Invoke(()=>{...});
...
}

And

pubic void Shutdown(){
   cacheLock.EnterWriteLock();
   try {
      if(Dispatcher.HasShutdownStarted()) return;
      Dispatcher.InvokeShutdown();
   } finally {
      cacheLock.ExitWriteLock();
   }
}
  • Related