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();
}
}