For doing asynchronous file IO I have made a class that lets me lock based upon a string key to prevent doing multiple writes at the same time to the same file or prevent having both writes and reads be done at the same time. The problem I face however is that its possible to grow the _lockDict by sending a different key each time. The old unused locks are not cleaned up but I do not know how I could do this in a threadsafe manner. This could potentially lead to a very big memory consumption.
The class that gives back a instance of a lock based upon a key:
public class AsyncNamedReaderWriterLocker
{
private readonly object _mutex = new object();
private readonly Dictionary<string, AsyncReaderWriterLock> _lockDict = new Dictionary<string, AsyncReaderWriterLock>();
public Task<IDisposable> EnterReaderLockAsync(string name)
{
var locker = GetLock(name);
return locker.EnterReaderLockAsync();
}
public Task<IDisposable> EnterWriterLockAsync(string name)
{
var locker = GetLock(name);
return locker.EnterWriterLockAsync();
}
private AsyncReaderWriterLock GetLock(string name)
{
lock (_mutex)
{
if (!_lockDict.TryGetValue(name, out AsyncReaderWriterLock locker))
{
locker = new AsyncReaderWriterLock();
_lockDict.Add(name, locker);
}
return locker;
}
}
And the lock itself (idea from: https://blogs.msdn.microsoft.com/pfxteam/2012/02/12/building-async-coordination-primitives-part-7-asyncreaderwriterlock/):
public class AsyncReaderWriterLock
{
private readonly Queue<TaskCompletionSource<IDisposable>> _writerQueue = new Queue<TaskCompletionSource<IDisposable>>();
private readonly Queue<TaskCompletionSource<IDisposable>> _readerQueue = new Queue<TaskCompletionSource<IDisposable>>();
private readonly WriterLocker _writerLocker;
private readonly ReaderLocker _readerLocker;
private readonly object _mutex = new object();
private int _locksHeld;
public AsyncReaderWriterLock()
{
_writerLocker = new WriterLocker(this);
_readerLocker = new ReaderLocker(this);
}
public Task<IDisposable> EnterReaderLockAsync()
{
lock (_mutex)
{
if (_locksHeld >= 0 && _writerQueue.Count == 0)
{
_locksHeld++;
return Task.FromResult<IDisposable>(_readerLocker);
}
var tcs = new TaskCompletionSource<IDisposable>();
_readerQueue.Enqueue(tcs);
return tcs.Task;
}
}
public Task<IDisposable> EnterWriterLockAsync()
{
lock (_mutex)
{
if (_locksHeld == 0)
{
_locksHeld = -1;
return Task.FromResult<IDisposable>(_writerLocker);
}
var tcs = new TaskCompletionSource<IDisposable>();
_writerQueue.Enqueue(tcs);
return tcs.Task;
}
}
private void ReleaseLocks()
{
if (_locksHeld != 0)
return;
// Give priority to writers.
if (_writerQueue.Count != 0)
{
_locksHeld = -1;
var tcs = _writerQueue.Dequeue();
tcs.TrySetResult(_writerLocker);
return;
}
// Then to readers.
while (_readerQueue.Count != 0)
{
var tcs = _readerQueue.Dequeue();
tcs.TrySetResult(_readerLocker);
++_locksHeld;
}
}
private void ReleaseReaderLock()
{
lock (_mutex)
{
_locksHeld--;
ReleaseLocks();
}
}
private void ReleaseWriterLock()
{
lock (_mutex)
{
_locksHeld = 0;
ReleaseLocks();
}
}
private class ReaderLocker : IDisposable
{
private readonly AsyncReaderWriterLock _asyncReaderWriterLock;
internal ReaderLocker(AsyncReaderWriterLock asyncReaderWriterLock)
{
_asyncReaderWriterLock = asyncReaderWriterLock;
}
public void Dispose()
{
_asyncReaderWriterLock.ReleaseReaderLock();
}
}
private class WriterLocker : IDisposable
{
private readonly AsyncReaderWriterLock _asyncReaderWriterLock;
internal WriterLocker(AsyncReaderWriterLock asyncReaderWriterLock)
{
_asyncReaderWriterLock = asyncReaderWriterLock;
}
public void Dispose()
{
_asyncReaderWriterLock.ReleaseWriterLock();
}
}
}
Example of using the AsyncNamedReaderWriterLocker class:
public class AsyncFileIO
{
private static readonly AsyncNamedReaderWriterLocker AsyncNamedReaderWriterLocker = new AsyncNamedReaderWriterLocker();
public async Task CreateFile(string filename, byte[] data)
{
using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
{
var directory = Path.GetDirectoryName(filename);
if (!Directory.Exists(directory))
{
Directory.CreateDirectory(directory);
}
using (var stream = File.Create(filename))
{
await stream.WriteAsync(data, 0, data.Length);
}
}
}
public async Task<byte[]> ReadFile(string filename)
{
using (await AsyncNamedReaderWriterLocker.EnterReaderLockAsync(filename))
{
if (!File.Exists(filename)) return null;
using (var stream = File.OpenRead(filename))
{
var data = new byte[stream.Length];
await stream.ReadAsync(data, 0, 0);
return data;
}
}
}
public async Task DeleteFile(string filename)
{
using (await AsyncNamedReaderWriterLocker.EnterWriterLockAsync(filename))
{
var directoryName = Path.GetDirectoryName(filename);
if (!Directory.Exists(directoryName)) return;
File.Delete(filename);
}
}
}
Note iam mostly interested in this for learning purposes and fully realize that this is overkill for most applications.
At first I was running into deadlocks when trying to synchronize the async named lock in order to remove the unused locks. This was because not all locks were released in the same order. Fixing this needed a specialized AsyncReaderWriter lock just for use with this async named lock. It now looks like this:
public class NamedAsyncReaderWriterLockController<TKey>
{
private readonly object _mutex = new object();
private readonly Dictionary<TKey, NamedAsyncReaderWriterLock> _lockDict = new Dictionary<TKey, NamedAsyncReaderWriterLock>();
public Task<IDisposable> EnterReaderLockAsync(TKey name)
{
lock (_mutex)
{
var locker = GetLock(name);
return locker.EnterReaderLockAsync();
}
}
public Task<IDisposable> EnterWriterLockAsync(TKey name)
{
lock (_mutex)
{
var locker = GetLock(name);
return locker.EnterWriterLockAsync();
}
}
private NamedAsyncReaderWriterLock GetLock(TKey name)
{
NamedAsyncReaderWriterLock locker;
if (!_lockDict.TryGetValue(name, out locker))
{
locker = new NamedAsyncReaderWriterLock(this, name, _mutex);
_lockDict.Add(name, locker);
}
return locker;
}
private void RemoveLock(TKey name)
{
_lockDict.Remove(name);
}
private class NamedAsyncReaderWriterLock
{
private readonly TKey _name;
private readonly NamedAsyncReaderWriterLockController<TKey> _namedAsyncReaderWriterLockController;
private readonly Queue<TaskCompletionSource<IDisposable>> _writerQueue = new Queue<TaskCompletionSource<IDisposable>>();
private readonly Queue<TaskCompletionSource<IDisposable>> _readerQueue = new Queue<TaskCompletionSource<IDisposable>>();
private readonly NamedWriterLock _namedWriterLock;
private readonly NamedReaderLock _namedReaderLock;
private readonly object _mutex = new object();
private readonly object _releaseMutex;
private int _locksHeld;
public NamedAsyncReaderWriterLock(NamedAsyncReaderWriterLockController<TKey> namedAsyncReaderWriterLockController, TKey name, object releaseMutex)
{
_namedWriterLock = new NamedWriterLock(this);
_namedReaderLock = new NamedReaderLock(this);
_releaseMutex = releaseMutex;
_name = name;
_namedAsyncReaderWriterLockController = namedAsyncReaderWriterLockController;
}
public Task<IDisposable> EnterReaderLockAsync()
{
lock (_mutex)
{
if (_locksHeld >= 0 && _writerQueue.Count == 0)
{
_locksHeld++;
return Task.FromResult<IDisposable>(_namedReaderLock);
}
var tcs = new TaskCompletionSource<IDisposable>();
_readerQueue.Enqueue(tcs);
return tcs.Task;
}
}
public Task<IDisposable> EnterWriterLockAsync()
{
lock (_mutex)
{
if (_locksHeld == 0)
{
_locksHeld = -1;
return Task.FromResult<IDisposable>(_namedWriterLock);
}
var tcs = new TaskCompletionSource<IDisposable>();
_writerQueue.Enqueue(tcs);
return tcs.Task;
}
}
private void ReleaseLocks()
{
if (_locksHeld != 0)
return;
// Give priority to writers.
if (_writerQueue.Count != 0)
{
_locksHeld = -1;
var tcs = _writerQueue.Dequeue();
tcs.TrySetResult(_namedWriterLock);
return;
}
// Then to readers.
while (_readerQueue.Count != 0)
{
var tcs = _readerQueue.Dequeue();
tcs.TrySetResult(_namedReaderLock);
++_locksHeld;
}
if (_locksHeld == 0) _namedAsyncReaderWriterLockController.RemoveLock(_name);
}
private void ReleaseReaderLock()
{
lock (_releaseMutex)
{
lock (_mutex)
{
_locksHeld--;
ReleaseLocks();
}
}
}
private void ReleaseWriterLock()
{
lock (_releaseMutex)
{
lock (_mutex)
{
_locksHeld = 0;
ReleaseLocks();
}
}
}
private class NamedReaderLock : IDisposable
{
private readonly NamedAsyncReaderWriterLock _namedAsyncReaderWriterLock;
internal NamedReaderLock(NamedAsyncReaderWriterLock namedAsyncReaderWriterLock)
{
_namedAsyncReaderWriterLock = namedAsyncReaderWriterLock;
}
public void Dispose()
{
_namedAsyncReaderWriterLock.ReleaseReaderLock();
}
}
private class NamedWriterLock : IDisposable
{
private readonly NamedAsyncReaderWriterLock _namedAsyncReaderWriterLock;
internal NamedWriterLock(NamedAsyncReaderWriterLock namedAsyncReaderWriterLock)
{
_namedAsyncReaderWriterLock = namedAsyncReaderWriterLock;
}
public void Dispose()
{
_namedAsyncReaderWriterLock.ReleaseWriterLock();
}
}
}
Related
I implemented a version of Redis Connection Pool manager (built on top of others' work) that is instantiated by a (client) static method in a static class. Currently, I am facing tons of connectionException errors in AWS.
I am concerned that the connection pool may not be working as intended. Therefore, I am asking for a code review of RedisCacheConnectionPoolManager implementation to identify any obvious problems with it. Thanks in advance.
public class RedisCacheConnectionPoolManager : IRedisCacheConnectionPoolManager
{
private static ConcurrentBag<Lazy<ConnectionMultiplexer>> _connections;
private readonly ConfigurationOptions _configurationOptions;
private readonly int _poolSize;
private const int MAX_CONNECTIONS = 10000;
private const int MIN_CONNECTIONS = 1;
private bool _disposed = false;
public RedisCacheConnectionPoolManager(int poolSize, string connectionString) : this(poolSize, ConfigurationOptions.Parse(connectionString))
{
}
public RedisCacheConnectionPoolManager(int poolSize, ConfigurationOptions configurationOptions)
{
if (poolSize < MIN_CONNECTIONS || poolSize > MAX_CONNECTIONS) throw new ArgumentException(string.Format("Pool size parameter must be within {0} and {1}.", MIN_CONNECTIONS.ToString(), MAX_CONNECTIONS.ToString()));
if (configurationOptions == null) throw new ArgumentNullException(string.Format("Configuration Options parameter cannot be null."));
_poolSize = poolSize;
_configurationOptions = configurationOptions;
Initialize();
}
// Destructor
~RedisCacheConnectionPoolManager()
{
Dispose(false);
}
public IConnectionMultiplexer GetConnection()
{
var loadedLazies = _connections.Where(x => x.IsValueCreated);
if (loadedLazies.Count() == _connections.Count)
{
return _connections.OrderBy(x => x.Value.GetCounters().TotalOutstanding).First().Value;
}
return _connections.First(x => !x.IsValueCreated).Value;
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private void Initialize()
{
_connections = new ConcurrentBag<Lazy<ConnectionMultiplexer>>();
for (int i = 0; i < _poolSize; i++)
{
_connections.Add(new Lazy<ConnectionMultiplexer>(() => ConnectionMultiplexer.Connect(_configurationOptions)));
}
}
protected virtual void Dispose(bool disposing)
{
try
{
if (!_disposed)
{
if (disposing)
{
// free all managed resource
var activeConnections = _connections.Where(lazy => lazy.IsValueCreated).ToList();
activeConnections.ForEach(connection => connection.Value.Dispose());
while (!_connections.IsEmpty)
{
_connections.TryTake(out var taken);
}
}
}
// free all unmanaged resources here
}
finally
{
_disposed = true;
}
}
}
public static class RedisClient()
{
private static IRedisCacheConnectionPoolManager _connectionPoolManager;
public static IConnectionMultiplexer GetConnnection()
{
return _connectionPoolManager.GetConnection();
}
private static IRedisCacheConnectionPoolManager GetConnectionPoolManager()
{
if (_connectionPoolManager == null)
{
lock (_lock)
{
if (_connectionPoolManager == null
{
try
{
_connectionPoolManager = new RedisCacheConnectionPoolManager(config.PoolSize, config.Configuration);
}
catch (Exception ex)
{
// handle error and log
}
}
}
}
return _connectionPoolManager;
}
I'm trying to write a class that continuosly ping "in background" a list of one or more devices, and report the results to a Progress object in the GUI main class. Im very new with async/await and Tasks, but my code seems to work fine on my pc, even if i know its not so good designed. But when i start the application in a different computer it throws an InvalidOperationException from the Progress object because the Dictionary in is changed. I think it happens when i assign a new PingService to the same variable.
The PingService class starts pinging (SendPingAsync) a list of IP's on a function with a inf. loop that updates the PingServiceReply object for each iteration of the list and then "reporting" it to the Progress object.
What is the way for get it done, and what im doing wrong?
Here's the code.
PingSeviceReply class
public class PingServiceReply
{
private Dictionary<string, bool> ipDictionary;
internal PingServiceReply()
{
ipDictionary = new Dictionary<string, bool>();
}
public void SetIpStatus(string ip, bool status)
{
if (ipDictionary.ContainsKey(ip))
{
ipDictionary[ip] = status;
}
else
{
ipDictionary.Add(ip, status);
}
}
public Dictionary<string, bool> GetStatusDictionary()
{
return ipDictionary;
}
public bool AreAllOnline()
{
foreach (bool value in ipDictionary.Values)
{
if (!value)
{
return false;
}
}
return true;
}
}
PingService class
public class PingService
{
private string name;
private List<string> ipList;
private IProgress<PingServiceReply> progress;
private CancellationTokenSource cts;
private int timeout;
private Task tsk;
public PingService(string name, List<string> ipList, int timeoutInMillliseconds, IProgress<PingServiceReply> progress)
{
this.name = name;
this.ipList = ipList;
this.progress = progress;
this.timeout = timeoutInMillliseconds;
}
public PingService(string name, string ip, int timeoutInMillliseconds, IProgress<PingServiceReply> progress)
{
this.name = name;
this.ipList = new List<string>();
ipList.Add(ip);
this.progress = progress;
this.timeout = timeoutInMillliseconds;
}
public PingService()
{
cts = new CancellationTokenSource();
}
private async Task ContinuousPingAsync()
{
var ping = new System.Net.NetworkInformation.Ping();
PingReply singleResponse;
PingServiceReply ruleResponse = new PingServiceReply();
while (!cts.Token.IsCancellationRequested)
{
foreach (string ip in ipList)
{
singleResponse = await ping.SendPingAsync(ip, timeout).ConfigureAwait(false);
ruleResponse.SetIpStatus(ip, singleResponse.Status == IPStatus.Success);
}
progress.Report(ruleResponse);
}
}
public void StartService()
{
cts = new CancellationTokenSource();
tsk = ContinuousPingAsync();
}
public void StopService()
{
cts.Cancel();
}
public string GetName()
{
return name;
}
}
The progress object
IProgress<PingServiceReply> pingProgress = new Progress<PingServiceReply>(HandleSinglePing);
private void HandleSinglePing(PingServiceReply report)
{
if (report.AreAllOnline())
{
//online
}
else
{
//offline
}
}
How i use all in the gui class, on the combobox selectedItemChanged event
comboService.StopService();
comboService = new PingService("Combo", (string) comboBox.SelectedItem, 1000, pingProgress);
comboService.StartService();
I'm developing an application for detecting motion within webcam frames.
For this, I'm using IBasicVideoEffect for extracting frames one by one from MediaCapture. I have created class CustomEffect which inherits IBasicVideoEffect. I have used OpenCV for motion detection, it is working fine. It is also giving me motion detection level. I want to raise event from CustomEffect if motion level is greater than threshold.
But for videoDefination code is:
var videoDefinition = new VideoEffectDefinition(typeof(CustomEffect).ToString());
Here for videoDefinition constructor it is asking for ClassID,
How can i get event from CustomEffect object.
I want to raise custom event from CustomEffect (eg.: MotionDetectedEvent )
Here is my CustomEffect class:
public sealed class CustomEffect : IBasicVideoEffect
{
private OpenCVHelper _helper;
private IPropertySet _configuration;
internal event EventHandler<EventArgs> MotionDetected;
public void SetProperties(IPropertySet configuration)
{
_configuration = configuration;
}
public void SetEncodingProperties(VideoEncodingProperties encodingProperties,
IDirect3DDevice device)
{
}
private bool IsToDetectMotion
{
get
{
object val;
if (_configuration != null &&
_configuration.TryGetValue("IsToDetectMotion", out val))
return (bool) val;
return false;
}
}
public void ProcessFrame(ProcessVideoFrameContext context)
{
var tempBitmap = context.OutputFrame.SoftwareBitmap;
context.InputFrame.SoftwareBitmap.CopyTo(tempBitmap);
var originalBitmap = SoftwareBitmap.Convert(tempBitmap, BitmapPixelFormat.Bgra8,
BitmapAlphaMode.Straight);
var outputBitmap = new SoftwareBitmap(BitmapPixelFormat.Bgra8,
originalBitmap.PixelWidth, originalBitmap.PixelHeight,
BitmapAlphaMode.Straight);
if (!IsToDetectMotion)
{
context.InputFrame.SoftwareBitmap.CopyTo(context.OutputFrame.SoftwareBitmap);
return;
}
if (_helper == null)
_helper = new OpenCVHelper();
var level = _helper.MotionDetector(tempBitmap, outputBitmap);
RaiseMotionDetectedEvent();
Debug.WriteLine(level.ToString());
outputBitmap.CopyTo(context.OutputFrame.SoftwareBitmap);
}
private void RaiseMotionDetectedEvent()
{
if (MotionDetected != null)
MotionDetected(this, new EventArgs());
}
public void Close(MediaEffectClosedReason reason)
{
}
public void DiscardQueuedFrames()
{
}
public bool IsReadOnly { get; }
public IReadOnlyList<VideoEncodingProperties> SupportedEncodingProperties
{
get
{
var encodingProperties = new VideoEncodingProperties();
encodingProperties.Subtype = "ARGB32";
return new List<VideoEncodingProperties> {encodingProperties};
// If the list is empty, the encoding type will be ARGB32.
// return new List<VideoEncodingProperties>();
}
}
public MediaMemoryTypes SupportedMemoryTypes { get; }
public bool TimeIndependent { get; }
}
//in Windows Runtime Component
public sealed class FrameArgs
{
public FrameArgs(int frameCount)
{
FrameCount = frameCount;
}
public int FrameCount
{ get; }
}
public sealed partial class CustomEffect
{
#region ProcessFrameCompleted
public EventHandler<Object> ProcessFrameCompleted
{
get
{
object val;
if (configuration != null && configuration.TryGetValue(nameof(ProcessFrameCompleted), out val))
{
return (EventHandler<Object>)val;
}
return null;
}
}
public void RaiseProcessFrameCompleted(FrameArgs args)
{
ProcessFrameCompleted?.Invoke(null, (Object)args);
}
#endregion
//call as necessary
//RaiseProcessFrameCompleted(new FrameArgs(frameCount));
}
//in your app
public static async Task<IMediaExtension> AddCustomEffect(MediaCapture mediaCapture, EventHandler<FrameArgs> callBack)
{
if (mediaCapture == null)
{
throw new ArgumentException("Parameter cannot be null", nameof(mediaCapture));
}
var videoEffectDefinition =
// ReSharper disable once AssignNullToNotNullAttribute
new VideoEffectDefinition(typeof(CustomEffect).FullName);
var videoEffect =
await mediaCapture.AddVideoEffectAsync(videoEffectDefinition, MediaStreamType.VideoPreview);
videoEffect.SetProperties(
new PropertySet()
{
{
"ProcessFrameCompleted",
new EventHandler<object>((sender, e) =>
{
var args = (FrameArgs)e;
int frameCount = args.FrameCount;
callBack?.Invoke(sender, args);
})
}
});
return videoEffect;
}
I'm using Windows Workflow and am hosting a Process host in a normal C# Class file which is used by WCF classes (hosted in IIS). The host is not hosted as a service, it is a normal class. I've read lots of articles and looked at many example and created my class accordingly. It seems to work perfectly in a single user test situation but fails occassionally in multi-user situations. Please can you advise if I'm on the right track or doing something fundamentally wrong? Thanks.
Below is my code:
public sealed class PurchaseRequisitionProcessHost : IPurchaseRequisitionProcessHost
{
public event PurchaseRequisitionWfIdleEventHandler PurchaseRequisitionWf_Idle;
public delegate void PurchaseRequisitionWfIdleEventHandler(PurchaseRequisitionWorkflowApplicationIdleEventArgs e);
private static volatile PurchaseRequisitionProcessHost _instance = null;
private static object syncRoot = new Object();
private AutoResetEvent instanceUnloaded = null;
public static PurchaseRequisitionProcessHost Instance
{
get
{
if(_instance == null)
{
lock(syncRoot)
{
if(_instance == null)
{
_instance = new PurchaseRequisitionProcessHost();
}
}
}
return _instance;
}
}
private PurchaseRequisitionProcessHost()
{
}
protected void OnPurchaseRequisitionWf_Idle(PurchaseRequisitionWorkflowApplicationIdleEventArgs e)
{
// Make a temporary copy of the event to avoid possibility of
// a race condition if the last subscriber unsubscribes
// immediately after the null check and before the event is raised.
PurchaseRequisitionWfIdleEventHandler handler = PurchaseRequisitionWf_Idle;
// Event will be null if there are no subscribers
if(handler != null)
{
// Use the () operator to raise the event.
handler(e);
}
}
// executed when instance goes idle
public void OnIdle(WorkflowApplicationIdleEventArgs e)
{
PurchaseRequisitionWorkflowApplicationIdleEventArgs args = new PurchaseRequisitionWorkflowApplicationIdleEventArgs();
if(e.Bookmarks != null && e.Bookmarks.Any())
{
args.BookMarkName = e.Bookmarks[0].BookmarkName;
string prNumber = args.BookMarkName.Substring(args.BookMarkName.IndexOf("_") + 1);
prNumber = prNumber.Substring(0, prNumber.IndexOf("_"));
args.PrNumber = prNumber;
}
args.InstanceId = e.InstanceId;
PurchaseRequisitionWf_Idle(args);
}
public PersistableIdleAction OnIdleAndPersistable(WorkflowApplicationIdleEventArgs e)
{
return PersistableIdleAction.Unload;
}
public void OnWorkFlowUnload(WorkflowApplicationEventArgs e)
{
CleanUpSubscribedEvents();
instanceUnloaded.Set();
}
//// executed when instance has completed
public void OnWorkflowCompleted(WorkflowApplicationCompletedEventArgs e)
{
CleanUpSubscribedEvents();
instanceUnloaded.Set();
}
//// executed when instance has aborted
public void OnWorkflowAborted(WorkflowApplicationAbortedEventArgs e)
{
CleanUpSubscribedEvents();
instanceUnloaded.Set();
}
// executed when unhandled exception
public UnhandledExceptionAction OnWorkflowUnhandledException(WorkflowApplicationUnhandledExceptionEventArgs e)
{
Helpers.Common.Common.logger.Error(string.Format("Unhandled WorkflowException for instance {0}", e.InstanceId), e.UnhandledException);
CleanUpSubscribedEvents();
instanceUnloaded.Set();
return UnhandledExceptionAction.Terminate;
}
private void CleanUpSubscribedEvents()
{
if(PurchaseRequisitionWf_Idle != null)
{
Delegate[] clientList = PurchaseRequisitionWf_Idle.GetInvocationList();
if(clientList != null && clientList.Any())
{
foreach(Delegate d in clientList)
{
PurchaseRequisitionWf_Idle -= (d as PurchaseRequisitionWfIdleEventHandler);
}
}
}
}
#region IPurchaseRequisitionProcessHost Members
public WorkflowApplication CreateAndRun(PurchaseRequisition pr, string uploadsPath)
{
return CreateAndRun(pr, uploadsPath, null);
}
// creates a workflow application, binds parameters, links extensions and run it
public WorkflowApplication CreateAndRun(PurchaseRequisition pr, string uploadsPath, PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
WorkflowApplication instance = null;
try
{
instanceUnloaded = new AutoResetEvent(false);
using(var instanceStore = new DisposableStore())
{
// input parameters for the WF program
IDictionary<string, object> inputs = new Dictionary<string, object>();
inputs.Add("PurchaseRequisition", pr);
inputs.Add("UploadsPath", uploadsPath);
instance = new WorkflowApplication(new PurchaseRequisitionProcessWorkflow(), inputs);
instance.InstanceStore = instanceStore.Store;
// Add the custom tracking participant
instance.Extensions.Add(new CustomTrackingParticipant());
instance.PersistableIdle += OnIdleAndPersistable;
instance.Completed += OnWorkflowCompleted;
instance.Unloaded += OnWorkFlowUnload;
instance.Aborted += OnWorkflowAborted;
instance.OnUnhandledException += OnWorkflowUnhandledException;
instance.Idle += OnIdle;
if(idleDelegate != null)
{
PurchaseRequisitionWf_Idle -= idleDelegate;
PurchaseRequisitionWf_Idle += idleDelegate;
}
// continue executing this instance
instance.Run();
instanceUnloaded.WaitOne();
}
}
catch(Exception ex)
{
Helpers.Common.Common.logger.Error(ex.Message, ex);
}
return instance;
}
public BookmarkResumptionResult SubmitApprovalDecision(Guid instanceId, string prNumber, string approvalSource, bool approved)
{
return SubmitApprovalDecision(instanceId, prNumber, approvalSource, approved, null);
}
public BookmarkResumptionResult SubmitApprovalDecision(Guid instanceId, string prNumber, string approvalSource, bool approved, PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
BookmarkResumptionResult brr = BookmarkResumptionResult.NotReady;
WorkflowApplication instance = this.LoadInstance(instanceId, idleDelegate);
string bookmarkName = string.Format("Pr_{0}_waitingForApprovalFrom_{1}", prNumber, approvalSource);
if(instance != null && instance.GetBookmarks().Count > 0)
{
brr = instance.ResumeBookmark(bookmarkName, approved);
}
instanceUnloaded.WaitOne();
return brr;
}
public BookmarkResumptionResult ReceiptPrGoods(Guid instanceId, PurchaseRequisitionReceipt pr)
{
return ReceiptPrGoods(instanceId, pr, null);
}
public BookmarkResumptionResult ReceiptPrGoods(Guid instanceId, PurchaseRequisitionReceipt pr, PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
BookmarkResumptionResult brr = BookmarkResumptionResult.NotReady;
WorkflowApplication instance = this.LoadInstance(instanceId, idleDelegate);
string bookmarkName = string.Format("Pr_{0}_waitingForReceiptOfGoods", pr.PrNumber);
if(instance != null && instance.GetBookmarks().Count > 0)
{
brr = instance.ResumeBookmark(bookmarkName, pr);
}
instanceUnloaded.WaitOne();
return brr;
}
public void UnsubscribePurchaseRequisitionWfIdleEvent(PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
PurchaseRequisitionWf_Idle -= idleDelegate;
}
#endregion
#region IProcessHost Members
// returns true if the instance is waiting (has pending bookmarks)
public bool IsInstanceWaiting(Guid instanceId)
{
bool isWaiting = false;
WorkflowApplication instance = LoadInstance(instanceId);
if(instance != null)
{
isWaiting = instance.GetBookmarks().Count > 0;
}
return isWaiting;
}
public WorkflowApplication LoadInstance(Guid instanceId)
{
return LoadInstance(instanceId, null);
}
// load and resume a workflow instance. If the instance is in memory,
// will return the version from memory. If not, will load it from the
// persistent store
public WorkflowApplication LoadInstance(Guid instanceId, PurchaseRequisitionWfIdleEventHandler idleDelegate)
{
WorkflowApplication instance = null;
try
{
instanceUnloaded = new AutoResetEvent(false);
using(var instanceStore = new DisposableStore())
{
instance = new WorkflowApplication(new PurchaseRequisitionProcessWorkflow());
WorkflowApplicationInstance wfAppInstance = WorkflowApplication.GetInstance(instanceId, instanceStore.Store);
// Add the custom tracking participant
instance.Extensions.Add(new CustomTrackingParticipant());
instance.PersistableIdle += OnIdleAndPersistable;
instance.Completed += OnWorkflowCompleted;
instance.Unloaded += OnWorkFlowUnload;
instance.Aborted += OnWorkflowAborted;
instance.OnUnhandledException += OnWorkflowUnhandledException;
instance.Idle += OnIdle;
if(idleDelegate != null)
{
PurchaseRequisitionWf_Idle -= idleDelegate;
PurchaseRequisitionWf_Idle += idleDelegate;
}
try
{
instance.Load(wfAppInstance);
}
catch(InstanceOwnerException)
{
}
}
}
catch(Exception ex)
{
Helpers.Common.Common.logger.Error(ex.Message, ex);
}
return instance;
}
#endregion
}
public class PurchaseRequisitionWorkflowApplicationIdleEventArgs : EventArgs
{
public string PrNumber
{
get;
set;
}
public Guid InstanceId
{
get;
set;
}
public string BookMarkName
{
get;
set;
}
}
public class DisposableStore : IDisposable
{
private string connectionString = Properties.Settings.Default.SqlPersistenceStoreDbConnectionString;
private SqlWorkflowInstanceStore _Store = null;
private InstanceHandle _handle = null;
public SqlWorkflowInstanceStore Store
{
get
{
if(_Store == null)
{
_Store = new SqlWorkflowInstanceStore(connectionString);
_Store.HostLockRenewalPeriod = TimeSpan.FromSeconds(5);
_Store.MaxConnectionRetries = 25;
_Store.RunnableInstancesDetectionPeriod = TimeSpan.FromSeconds(10);
_Store.InstanceCompletionAction = InstanceCompletionAction.DeleteAll;
_Store.InstanceLockedExceptionAction = InstanceLockedExceptionAction.BasicRetry;
_handle = _Store.CreateInstanceHandle();
CreateWorkflowOwnerCommand createOwnerCmd = new CreateWorkflowOwnerCommand();
InstanceView view = _Store.Execute(_handle, createOwnerCmd, TimeSpan.FromSeconds(5));
_Store.DefaultInstanceOwner = view.InstanceOwner;
}
return _Store;
}
}
public void Dispose()
{
_handle.Free();
_handle = _Store.CreateInstanceHandle(_Store.DefaultInstanceOwner);
try
{
_Store.Execute(_handle, new DeleteWorkflowOwnerCommand(), TimeSpan.FromSeconds(10));
}
catch(InstancePersistenceCommandException ex)
{
Helpers.Common.Common.logger.Info(ex.Message);
}
catch(InstanceOwnerException ex)
{
Helpers.Common.Common.logger.Info(ex.Message);
}
catch(OperationCanceledException ex)
{
Helpers.Common.Common.logger.Info(ex.Message);
}
catch(Exception ex)
{
Helpers.Common.Common.logger.Info(ex.Message);
}
finally
{
_handle.Free();
_Store.DefaultInstanceOwner = null;
_handle = null;
_Store = null;
}
}
}
I am trying to implement a mechanism where lock ordering is automatically checked and an exception is thrown when locks are acquired out of order at runtime to avoid deadlocks. A reference implementation is below. Please let me know if you see any issues with this implementation. Many thanks.
public class someResource
{
private OrderedLock lock1 = new OrderedLock(1);
private OrderedLock lock2 = new OrderedLock(2);
public void lockInOrder()
{
lock1.AcquireWriteLock();
lock2.AcquireWriteLock();
// do something
lock1.ReleaseWriteLock();
lock2.ReleaseWriteLock();
}
public void lockOutOfOrder()
{
lock2.AcquireReadLock();
lock1.AcquireReadLock(); // throws exception
// read something
lock2.ReleaseReadLock();
lock1.ReleaseReadLock();
}
}
public class OrderedLock : IDisposable
{
private static readonly ConcurrentDictionary<int, object> createdLocks = new ConcurrentDictionary<int, object>();
[ThreadStatic]
private static ISet<int> acquiredLocks;
private readonly ThreadLocal<int> refCount = new ThreadLocal<int>(false);
private readonly ReaderWriterLockSlim locker = new ReaderWriterLockSlim(LockRecursionPolicy.SupportsRecursion);
private readonly int id;
/// <exception cref="InvalidOperationException">Duplicate identifier detected</exception>
public OrderedLock(int id)
{
if (!createdLocks.TryAdd(id, null))
{
throw new InvalidOperationException("Duplicate identifier detected");
}
this.id = id;
this.refCount.Value = 0;
}
public void AcquireReadLock()
{
this.CheckLockOrder();
this.locker.EnterReadLock();
}
public void AcquireWriteLock()
{
this.CheckLockOrder();
this.locker.EnterWriteLock();
}
public void ReleaseReadLock()
{
this.refCount.Value--;
this.locker.ExitReadLock();
if (this.refCount.Value == 0)
{
acquiredLocks.Remove(this.id);
}
}
public void ReleaseWriteLock()
{
this.refCount.Value--;
this.locker.ExitWriteLock();
if (this.refCount.Value == 0)
{
acquiredLocks.Remove(this.id);
}
}
public void Dispose()
{
while (this.locker.IsWriteLockHeld)
{
this.ReleaseWriteLock();
}
while (this.locker.IsReadLockHeld)
{
ReleaseReadLock();
}
this.locker.Dispose();
this.refCount.Dispose();
GC.SuppressFinalize(this);
}
/// <exception cref="InvalidOperationException">Invalid order of locking detected</exception>
private void CheckLockOrder()
{
if (acquiredLocks == null)
{
acquiredLocks = new HashSet<int>();
}
if (!acquiredLocks.Contains(this.id))
{
if (acquiredLocks.Any() && acquiredLocks.Max() > this.id)
{
throw new InvalidOperationException("Invalid order of locking detected");
}
acquiredLocks.Add(this.id);
}
this.refCount.Value++;
}
}