I have a class that uses BlockingCollection like this:
public class Logger : IDisposable
{
private BlockingCollection<LogMessage> _messages = null;
private Thread _worker = null;
private bool _started = false;
public void Start()
{
if (_started) return;
//Some logic to open log file
OpenLogFile();
_messages = new BlockingCollection<LogMessage>(); //int.MaxValue is the default upper-bound
_worker = new Thread(Work) { IsBackground = true };
_worker.Start();
_started = true;
}
public void Stop()
{
if (!_started) return;
// prohibit adding new messages to the queue,
// and cause TryTake to return false when the queue becomes empty.
_messages.CompleteAdding();
// Wait for the consumer's thread to finish.
_worker.Join();
//Dispose managed resources
_worker.Dispose();
_messages.Dispose();
//Some logic to close log file
CloseLogFile();
_started = false;
}
/// <summary>
/// Implements IDiposable
/// In this case, it is simply an alias for Stop()
/// </summary>
void IDisposable.Dispose()
{
Stop();
}
/// <summary>
/// This is message consumer thread
/// </summary>
private void Work()
{
LogMessage message;
//Try to get data from queue
while(_messages.TryTake(out message, Timeout.Infinite))
WriteLogMessage(message); //... some simple logic to write 'message'
}
}
I create a new instance of the Logger class, call its Start() method. Then, if I forget to call the Dispose method when the instance is no longer referenced, then the Worker Thread will never end. That's a kind of memory leak. Am I right? and how to overcome this?
You may try to keep only a weak reference to the BlockingCollection in your worker thread and do not reference an object that is referencing the BlockingCollection. I made a static method to ensure that we don't reference the Logger instance this.
This way the collection can be finalized/collected when it is no longer referenced. I'm not sure it'll work, you have to try, It depends on whether TryTake keeps the collection alive or not. It may not work in debug as the GC behave differently, so try it in release without debugger attached.
public class Logger : IDisposable
{
private BlockingCollection<LogMessage> _messages = null;
private Thread _worker = null;
private bool _started = false;
public void Start()
{
if (_started) return;
//Some logic to open log file
OpenLogFile();
_messages = new BlockingCollection<LogMessage>(); //int.MaxValue is the default upper-bound
_worker = new Thread(Work) { IsBackground = true };
_worker.Start(new WeakReference<BlockingCollection<LogMessage>>(_messages));
_started = true;
}
public void Stop()
{
if (!_started) return;
// prohibit adding new messages to the queue,
// and cause TryTake to return false when the queue becomes empty.
_messages.CompleteAdding();
// Wait for the consumer's thread to finish.
_worker.Join();
//Dispose managed resources
_worker.Dispose();
_messages.Dispose();
//Some logic to close log file
CloseLogFile();
_started = false;
}
/// <summary>
/// Implements IDiposable
/// In this case, it is simply an alias for Stop()
/// </summary>
void IDisposable.Dispose()
{
Stop();
}
/// <summary>
/// This is message consumer thread
/// </summary>
private static void Work(object state)
{
LogMessage message;
//Try to get data from queue
do
{
BlockingCollection<LogMessage> messages;
if (((WeakReference<BlockingCollection<LogMessage>>)state).TryGetTarget(out messages)
&& messages.TryTake(out message, Timeout.Infinite))
{
WriteLogMessage(message); //... some simple logic to write 'message'
continue;
}
} while (false);
}
}
Related
I would like to use Condition Variable in order to know when Messages Queue is not empty, i would like to use it in "HandleMessageQueue" as a thread
private static Queue<Message> messages = new Queue<Message>();
/// <summary>
/// function return the first message
/// </summary>
/// <returns>first message element</returns>
public static Message GetFirst()
{
return messages.Dequeue();
}
in another class:
/// <summary>
/// Function run while the clients connected and handle the queue message
/// </summary>
public static void HandleMessageQueue()
{
// ...
}
What you're probably looking for is a simple producer-consumer pattern. In this case I'd recommend using .NET's BlockingCollection, which allows you to easily handle the following cases:
have one thread push stuff in a queue
have another thread block until stuff is available
make the whole thing easy to shutdown without having to forcibly terminate the thread
Here's a short code sample, read the comments for more information about what every bit does:
public class Queue : IDisposable
{
private readonly Thread _messageThread; // thread for processing messages
private readonly BlockingCollection<Message> _messages; // queue for messages
private readonly CancellationTokenSource _cancellation; // used to abort the processing when we're done
// initializes everything and starts a processing thread
public Queue()
{
_messages = new BlockingCollection<Message>();
_cancellation = new CancellationTokenSource();
_messageThread = new Thread(ProcessMessages);
_messageThread.Start();
}
// processing thread function
private void ProcessMessages()
{
try
{
while (!_cancellation.IsCancellationRequested)
{
// Take() blocks until either:
// 1) a message is available, in which case it returns it, or
// 2) the cancellation token is cancelled, in which case it throws an OperationCanceledException
var message = _messages.Take(_cancellation.Token);
// process the message here
}
}
catch (OperationCanceledException)
{
// Take() was cancelled, let the thread exit
}
}
// pushes a message
public void QueueMessage(Message message)
{
_messages.Add(message);
}
// stops processing and clean up resources
public void Dispose()
{
_cancellation.Cancel(); // let Take() abort by throwing
_messageThread.Join(); // wait for thread to exit
_cancellation.Dispose(); // release the cancellation source
_messages.Dispose(); // release the queue
}
}
Another option would be to combine a ConcurrentQueue<T> with a ManualResetEvent (events are roughly the .NET equivalent to condition variables), but that would be doing by hand what BlockingCollection<T> does).
something like this?
public class EventArgs<T> : EventArgs
{
private T eventData;
public EventArgs(T eventData)
{
this.eventData = eventData;
}
public T EventData
{
get { return eventData; }
}
}
public class ObservableQueue<T>
{
public event EventHandler<EventArgs<T>> EnQueued;
public event EventHandler<EventArgs<T>> DeQueued;
public int Count { get { return queue.Count; } }
private readonly Queue<T> queue = new Queue<T>();
protected virtual void OnEnqueued(T item)
{
if (EnQueued != null)
EnQueued(this, new EventArgs<T>(item));
}
protected virtual void OnDequeued(T item)
{
if (DeQueued != null)
DeQueued(this, new EventArgs<T>(item));
}
public virtual void Enqueue(T item)
{
queue.Enqueue(item);
OnEnqueued(item);
}
public virtual T Dequeue()
{
var item = queue.Dequeue();
OnDequeued(item);
return item;
}
}
and use it
static void Main(string[] args)
{
ObservableQueue<string> observableQueue = new ObservableQueue<string>();
observableQueue.EnQueued += ObservableQueue_EnQueued;
observableQueue.DeQueued += ObservableQueue_DeQueued;
observableQueue.Enqueue("abc");
observableQueue.Dequeue();
Console.Read();
}
We have a service which starts a process and waits for process to exit when service is stopped/ user of service calls stop (to stop/kill process started by service).
Sporadically, process.waitForExit(TimeSpan) hangs.
Please note that process started by Service is native process (C++/CLI) process and service is in C#.
Following is the code snippet we are using
public class ApplicationProcessControl : IProcessControl
{
private Process _proc;
private const int ProcessIdleTimeout = 5000;
public bool Start(string arguments)
{
if (IsAlive)
{
Log.TraceInfo("Application process already running. Killing it now...");
_proc.Kill();
}
var eProcStarted = new Mutex(false, "Mutex111");
_proc = new Process { EnableRaisingEvents = true, StartInfo = new ProcessStartInfo(_exePath, arguments) { RedirectStandardOutput = false,RedirectStandardError = false};
_proc.Exited += OnProcessExited;
_proc.Start();
bool started;
if(_proc == null)
{
Log.TraceInfo("Unable to start application process");
started = false;
}
else
{
started = eProcStarted.WaitOne(ProcessIdleTimeout);
if(started)
{
Log.TraceInfo($"Application process with id {_proc.Id} started successfully");
}
}
eProcStarted.Dispose();
return started;
}
public void Kill()
{
_proc.Kill();
}
public bool WaitForProcessToExit(TimeSpan timeout)
{
return _proc.WaitForExit((int) timeout.TotalMilliseconds);
}
public event Action ProcessExited;
private void OnProcessExited(object sender, EventArgs e)
{
var proc = sender as Process;
if(proc != null)
{
proc.Exited -= OnProcessExited;
if(proc.ExitCode == 0)
{
Log.TraceInfo("Application process exited gracefully");
}
else
{
Log.DeveloperWarning("Application process exited unexpectedly with code {0}", proc.ExitCode);
OnProcessExited();
}
}
}
private void OnProcessExited()
{
Action handler = ProcessExited;
handler?.Invoke();
}
}
public interface IProcessControl
{
bool IsAlive { get; }
bool Start(string arguments);
bool WaitForProcessToExit(TimeSpan timeout);
void Kill();
event Action ProcessExited;
}
public class ApplicationClientService: DisposableObject, IComponentService, ITaskControl, IUIControl,
IDataProvider<AngleFlavors>, IApplicationCloseNotifier
{
//...
private readonly IProcessControl _procCtrl;
public ApplicationClientService(IObjectProvider objPro)
{
//...
_procCtrl.ProcessExited += OnApplicationProcessExited;
}
public void Stop()
{
//...
CleanUpAppProcess();
//...
}
private void CleanUpAppProcess()
{
//...
if(!_procCtrl.WaitForProcessToExit(TimeSpan.FromSeconds(5)))
{
_procCtrl.Kill();
}
}
private void OnApplicationProcessExited()
{
if(!_isAppRunning)
{
return;
}
_isAppRunning = false;
_autoLaunchRequested = false;
RaiseApplicationClosed();
Log.DeveloperWarning("Application process closed unexpectedly");
Log.UserMessageApplicationClosedUnexpectedly();
...
}
protected virtual void RaiseApplicationClosed()
{
//AuditApplicationStop();
//ApplicationClosed?.Invoke();
}
}
Don't know if this can answer your question (I have myself more questions than answers on this), but this code:
private void CleanUpAppProcess()
{
//...
if(!_procCtrl.WaitForProcessToExit(TimeSpan.FromSeconds(5)))
{
_procCtrl.Kill();
}
}
calls WaitForExit before the Kill command. Are you expecting the process to terminate by itself / to be terminated by a user in between 5 seconds? As Bennie Zhitomirsky pointed out in his comment, the mutex is not owned when it should be (if I understood correctly what you want to achieve, if not, sorry). What about the implementation of IsAlive?
Anyway, I put down some lines for the ApplicationProcessControl class. I just tested it a bit with some native process and seems to work (but still, I'm not sure this is what you're trying to achive):
public class ApplicationProcessControl : IProcessControl
{
/// <summary>
/// Process instance variable.
/// </summary>
private Process _proc;
/// <summary>
/// The thread will try to acquire the mutex for a maximum of _mutexAcquireTimeout ms.
/// </summary>
private const int _mutexAcquireTimeout = 5000;
/// <summary>
/// Global static named mutex, seen by all threads.
/// </summary>
private static Mutex SpawnProcessMutex = new Mutex(false, "Mutex111");
/// <summary>
/// The state of the process.
/// </summary>
public bool IsAlive
{
get { return !(_proc is null) && !_proc.HasExited; }
}
/// <summary>
/// Spawns a new process.
/// </summary>
public bool Start(string arguments)
{
// Try to acquire the mutex for _mutexAcquireTimeout ms.
if (!SpawnProcessMutex.WaitOne(_mutexAcquireTimeout) || IsAlive)
{
// Mutex is still owned (another thread got it and is trying to run the process)
// OR the process is already running.
// DO NOT start a new process.
return false;
}
try
{
// Mutex is acquired by this thread.
// Create a new instance of the Process class.
_proc = new Process
{
EnableRaisingEvents = true,
StartInfo = new ProcessStartInfo("the_process_to_be_run", arguments)
{
RedirectStandardOutput = false,
RedirectStandardError = false
}
};
// Subscription to the ProcessExited event.
_proc.Exited += OnProcessExited;
// Run the process.
var haveAnHandle = _proc.Start();
// *******
// TODO: The process started but we may not have an handle to it. What to do?
// *******
//Log.TraceInfo($"Application process with id {_proc.Id} started successfully");
return true;
}
catch (Exception) // TODO: [Catch the specific exceptions here]
{
// The process failed to start, still we have an instance of Process with no use.
if (!(_proc is null))
{
_proc.Dispose();
_proc = null;
}
//Log.TraceInfo("Unable to start application process");
return false;
}
finally
{
// Release the mutex, another thread may be waiting to acquire it.
SpawnProcessMutex.ReleaseMutex();
}
}
/// <summary>
/// Kills the process started by the Start method.
/// </summary>
public void Kill()
{
if (IsAlive) _proc.Kill();
}
/// <summary>
/// Can't see a real reason to block the thread waiting synchronously for the process to
/// exit, we are already subscribed to the Exited event.
/// Kept here to avoid breaking the interface contract.
/// </summary>
public bool WaitForProcessToExit(TimeSpan timeout)
{
return _proc.WaitForExit((int)timeout.TotalMilliseconds);
}
/// <summary>
/// Client class consumable event to know the the process actually terminated.
/// </summary>
public event Action ProcessExited;
/// <summary>
/// Process Exited event handler.
/// </summary>
private void OnProcessExited(object sender, EventArgs e)
{
// Get a reference to the actual Process object.
var proc = sender as Process;
if (proc is null) return;
proc.Exited -= OnProcessExited;
if (proc.ExitCode == 0)
{
// Log.TraceInfo("Application process exited gracefully");
}
else
{
// Log.DeveloperWarning("Application process exited unexpectedly with code {0}", proc.ExitCode);
ProcessExited?.Invoke();
}
}
}
I have made a previous question with the same code, and have been advised to use ManualResetEvent, because it is the right way of doing what I want, and I agree with that.
Problem is: I have read and re-read the docs and a lot of tutorials about ManualResetEvent, with its WaitOne, Set, Unset and Reset methods, but frankly I didn't quite understand how they are supposed to be used.
What my code does: it keeps looking for connected devices, and when it finds one, it keeps verifying if it's still connected (otherwise, start looking for again). This is the "monitoring" activity, which can be started or stopped by client code using Start and Stop methods. There is also a _busy flag, so that Stop method only returns after one monitoring cycle is complete.
Fact is: currently the bool flag approach is not working, so I want to replace it with ManualResetEvent approach, but cannot figure out even how to start.
Should I replace the flags by ManualResetEvents, one-to-one?
Should SearchDevices() and MonitorDeviceConnection() methods conditionally run in the same thread, or should each one have (or be) its own thread?
How the difference between Start and Stop (turning on and off, called from client code) and "selecting" between both monitoring methods affect the way each ManualResetEvent is used? (not quite sure this question makes much sense)
Using an enum flag to select one of two possible paths of execution is quite a code smell, isn't it? What would be a sensible way of getting rid of it in a "ManualResetEvent context"?
Here's the code:
public class DeviceMonitor
{
bool _running;
bool _monitoring;
bool _busy = false;
MonitoringMode _monitoringMode;
Thread _monitoringThread;
readonly object _lockObj = new object();
// CONSTRUTOR
public DeviceMonitor()
{
_monitoringThread = new Thread(new ThreadStart(ExecuteMonitoring));
_monitoringThread.IsBackground = true;
_running = true;
_monitoringThread.Start();
}
public void Start()
{
_monitoring = true;
}
public void Stop()
{
_monitoring = false;
while (_busy)
{
Thread.Sleep(5);
}
}
void ExecuteMonitoring()
{
while (_running)
{
Console.WriteLine("ExecuteMonitoring()");
if (_monitoring)
{
lock (_lockObj)
{
_busy = true;
}
Console.WriteLine("busy");
if (_monitoringMode == MonitoringMode.SearchDevices)
{
SearchDevices();
}
else
if (_monitoringMode == MonitoringMode.MonitorDeviceConnection)
{
MonitorDeviceConnection();
}
lock (_lockObj)
{
_busy = false;
}
Console.WriteLine("not busy");
}
Thread.Sleep(1000);
_busy = false;
}
}
private void SearchDevices()
{
var connected = ListDevices();
if (connected.Count > 0)
{
Device = connected.First();
ToggleMonitoringMode();
}
else
Device = null;
}
void MonitorDeviceConnection()
{
if (Device == null)
{
ToggleMonitoringMode();
}
else
{
bool responding = Device.isConnected;
Console.WriteLine("responding " + responding);
if (!responding)
{
Device = null;
ToggleMonitoringMode();
}
}
}
void ToggleMonitoringMode()
{
if (_monitoringMode == MonitoringMode.SearchDevices)
_monitoringMode = MonitoringMode.MonitorDeviceConnection;
else
if (_monitoringMode == MonitoringMode.MonitorDeviceConnection)
_monitoringMode = MonitoringMode.SearchDevices;
}
enum MonitoringMode
{
SearchDevices,
MonitorDeviceConnection
}
}
There's a difference in ManualResetEvent and AutoResetEvent. You always have to reset the ManualResetEvents. you can imagine them like Manual door closing and Auto door closing when you enter in a room.
For ManualResetEvent you have to manually call the Reset() other wise the thread would keep on running unless you call the Reset.
I have tried to create a simulator for your problem using the AutoResetEvent. Hope this will give you more clear picture. Later if you want you can try changing and using ManualResetEvent as you may.
If you look at the Idea of this implementation then it works like this:
And this sequence goes on for switching states if necessary.
In this sample I'll be using 2 threads one for search device and other for Monitoring the device status. The RulyCanceler will be your token for cancellation. This would be the replacement of _busy flag that you have used.
public class DeviceMonitorSignaling
{
readonly object _lockObj = new object();
EventWaitHandle searchingHandle;
EventWaitHandle monitoringHandle;
bool _running;
bool _monitoring;
volatile Device device;
MonitoringMode _monitoringMode;
Thread _monitoringThread;
Thread _searchDeviceThread;
RulyCanceler CancelToken;
// CONSTRUTOR
public DeviceMonitorSignaling()
{
CancelToken = new RulyCanceler();
searchingHandle = new AutoResetEvent(false);
monitoringHandle = new AutoResetEvent(false);
_monitoringThread = new Thread
(() =>
{
try { MonitorDeviceConnection(CancelToken); }
catch (OperationCanceledException)
{
Console.WriteLine("Canceled Search!");
}
});
_searchDeviceThread = new Thread(() =>
{
try { SearchDevices(CancelToken); }
catch (OperationCanceledException)
{
Console.WriteLine("Canceled Monitor!");
}
});
_monitoringThread.IsBackground = true;
}
public void Start()
{
_monitoring = true;
_running = true;
_searchDeviceThread.Start();
_monitoringThread.Start();
}
public void Stop()
{
CancelToken.Cancel();
// One of the thread would be sleeping to identify and recall it.
WakeSleepingThread();
}
/// <summary>
/// Method to search the device.
/// </summary>
/// <param name="cancelToken"></param>
void SearchDevices(RulyCanceler cancelToken)
{
while (_running)
{
cancelToken.ThrowIfCancellationRequested();
Console.WriteLine("Searching devices....");
Thread.Sleep(1000);
device = new Device(); // may be some logic to detect the device.
Console.WriteLine("Finished!!! Searching devices. Start monitoring.");
if(device != null)
{
// Block the search thread and start the monitoring thread.
ToggleMonitoringMode();
}
}
}
/// <summary>
/// Once the device is detected
/// </summary>
/// <param name="cancelToken"></param>
void MonitorDeviceConnection(RulyCanceler cancelToken)
{
monitoringHandle.WaitOne();
Console.WriteLine("monitoring started.");
while (_monitoring)
{
cancelToken.ThrowIfCancellationRequested();
Thread.Sleep(1000);
if (device == null)
{
Console.WriteLine("Disconnected Invoking search.");
// Block monitoring thread and awake the device search.
ToggleMonitoringMode();
}
else
{
bool responding = device.isConnected;
Console.WriteLine("responding {0}", responding);
if (!responding)
{
Console.WriteLine("Not responding. Invoking search.");
device = null;
// Block monitoring thread and awake the device search.
ToggleMonitoringMode();
}
}
}
}
internal void ToggleMonitoringMode()
{
if (_monitoringMode == MonitoringMode.SearchDevices)
{
_monitoringMode = MonitoringMode.MonitorDeviceConnection;
monitoringHandle.Set();
searchingHandle.WaitOne();
}
else if (_monitoringMode == MonitoringMode.MonitorDeviceConnection)
{
_monitoringMode = MonitoringMode.SearchDevices;
searchingHandle.Set();
monitoringHandle.WaitOne();
}
}
internal void WakeSleepingThread()
{
if(_monitoringMode == MonitoringMode.MonitorDeviceConnection)
{
searchingHandle.Set();
}
else
{
monitoringHandle.Set();
}
}
enum MonitoringMode
{
SearchDevices,
MonitorDeviceConnection
}
/// <summary>
/// For test purpose remove the device.
/// </summary>
internal void DisconnectDevice()
{
if(device != null)
{
device = null;
}
}
/// <summary>
/// For test purpose change the device status
/// </summary>
internal void ChangeDeviceState()
{
if (device != null)
{
device.Disconnect();
}
}
/// <summary>
/// Dummy device
/// </summary>
internal class Device
{
public bool isConnected = false;
public Device()
{
isConnected = true;
}
public void Disconnect()
{
isConnected = false;
}
}
internal class RulyCanceler
{
object _cancelLocker = new object();
bool _cancelRequest;
public bool IsCancellationRequested
{
get { lock (_cancelLocker) return _cancelRequest; }
}
public void Cancel() { lock (_cancelLocker) _cancelRequest = true; }
public void ThrowIfCancellationRequested()
{
if (IsCancellationRequested) throw new OperationCanceledException();
}
}
}
If you look at the Stop() method this method is using the CancelToken to send an interrup signal. WakeSleepThread is used to wake the any of the sleeping thread out of two.
static void Main(string[] args)
{
var obj = new DeviceMonitorSignaling();
Console.WriteLine("Starting...");
obj.Start();
Thread.Sleep(4000); // after 4 sec remove the device.
Console.WriteLine("Changing device state.");
obj.DisconnectDevice();
Thread.Sleep(4000); // // after 4 sec change the device status.
obj.ChangeDeviceState();
Console.Read();
Console.WriteLine("Stopping...");
obj.Stop();
Console.Read();
}
I have used above simultion to change the device status and device object to set as null. If you run the program you'll see the output something like this.
Note: There might be areas to improve this sample code. Feel free to optimize as you may.
References - http://www.albahari.com/threading/
I have a service that is running constantly processing data, it receives requests to process new data through messaging. While it's busy processing new requests get merged together so that they are then all processed at once. An AutoResetEvent is used to notify the processor that a new request is available.
My question is in EventLoop should it be possible that currentRequest after the WaitOne to be null?
Is it bad practice to have the _eventAvailable.Set() outside of the lock(_eventLocker)? I moved it out so that it wouldn't start going at the WaitOne and immediately contest the lock(_eventLocker).
Any suggestions on how to better write the following code?
public sealed class RealtimeRunner : MarshalByRefObject
{
/// <summary>
/// The actual event, new events get merged into this if it is not null
/// </summary>
private Request _pendingRequest;
/// <summary>
/// Used to signal the runner thread when an event is available to process
/// </summary>
private readonly AutoResetEvent _eventAvailable = new AutoResetEvent(false);
private readonly object _eventLocker = new object();
/// <summary>
/// Called on a background thread via messaging
/// </summary>
public void QueueEvent(RealtimeProcessorMessage newRequest)
{
bool mergedRequest;
lock (_eventLocker)
{
if (_pendingRequest == null)
{
mergedRequest = false;
_pendingRequest = new Request(newRequest, _engine);
}
else
{
mergedRequest = true;
_pendingRequest.Merge(newRequest, _engine);
}
}
_eventAvailable.Set();
}
/// <summary>
/// This is running on its own thread
/// </summary>
private void EventLoop()
{
while (true)
{
// Block until something exists in _pendingRequest
_eventAvailable.WaitOne();
Request currentRequest;
lock (_eventLocker)
{
currentRequest = _pendingRequest;
_pendingRequest = null;
}
// CAN THIS EVER BE NULL?
if (currentRequest == null)
continue;
//do stuff with the currentRequest here
}
}
}
Yes, the if (currrentRequest == null) could evaluate to true. Consider two threads racing to call _eventAvailable.Set(). One completes the call and the other gets preempted. Meanwhile the EventLoop thread wakes up and completes an entire iteration of the loop. You now have a situation where _pendingRequest is null and the WaitHandle is still waiting to be signaled again.
I want to present an entirely different solution to the problem. It looks your code could be simplified by using the producer-consumer pattern. This pattern is most easily implemented using a blocking queue. The BlockingCollection class implements such a queue.
public sealed class RealtimeRunner : MarshalByRefObject
{
private BlockingCollection<Request> m_Queue = new BlockingCollection<Request>();
public void QueueEvent(RealtimeProcessorMessage newRequest)
{
m_Queue.Add(new Request(newRequest _engine));
}
private void EventLoop()
{
while (true)
{
// This blocks until an item appears in the queue.
Request request = m_Queue.Take();
// Process the request here.
}
}
}
I'm trying to create a second message loop to process/filter low level messages asynchronously in C#. It works by creating a hidden form, exposing it's Handle property to be hooked, and run a second message loop in a separate thread. At the moment I'm quite happy with the results but I'm unable to exit from the second loop properly. The only workaround was setting the IsBackground property to true, so the second thread will be simply terminated (without processing all the pending messages) at main application exit.
The question is: how to proper quit that message loop so the second Application.Run() returns? I tried differents approaches creating a separate ApplicationContext and controlling various events (Application.ApplicationExit, Application.ThreadExit, ApplicationContext.ThreadExit) but they all failed with race conditions I'm unable to debug.
Any hint? Thanks
This is the code:
public class MessagePump
{
public delegate void HandleHelper(IntPtr handle);
public MessagePump(HandleHelper handleHelper, Filter filter)
{
Thread thread = new Thread(delegate()
{
ApplicationContext applicationContext = new ApplicationContext();
Form form = new Form();
handleHelper(form.Handle);
Application.AddMessageFilter(new MessageFilter(filter));
Application.Run(applicationContext);
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = true; // <-- The workaround
thread.Start();
}
}
public delegate bool Filter(ref Message m);
internal class MessageFilter : IMessageFilter
{
private Filter _Filter;
public MessageFilter(Filter filter)
{
_Filter = filter;
}
#region IMessageFilter Members
public bool PreFilterMessage(ref Message m)
{
return _Filter(ref m);
}
#endregion // IMessageFilter Members
}
I use it in the main Form constructor in this way:
_Completion = new ManualResetEvent(false);
MessagePump pump = new MessagePump(
delegate(IntPtr handle)
{
// Sample code, I did this form twain drivers low level wrapping
_Scanner = new TwainSM(handle);
_Scanner.LoadDs("EPSON Perfection V30/V300");
},
delegate(ref Message m)
{
// Asyncrhronous processing of the messages
// When the correct message is found -->
_Completion.Set();
}
EDIT: Full solution in my answer.
You should pass the Form instance to the ApplicationContext ctor as a parameter:
applicationContext = new ApplicationContext(form);
Right now, you are basically creating a no-context instance, which doesn't care about your form being closed.
Also, it is a good practice to do some cleanup, like removing the filter when you don't need it anymore:
Form form = new Form();
ApplicationContext applicationContext = new ApplicationContext(form);
handleHelper(form.Handle);
MessageFilter filter = new MessageFilter(filter);
Application.AddMessageFilter(filter);
Application.Run(applicationContext);
Application.RemoveMessageFilter(filter);
[Edit]
If you don't want to show the form, then you can use the paramaterless ctor, but you will have to close the context manually by calling the ApplicationContext.ExitThread method. This method actually gets called when your form fires the FormClosed event, if you pass the form in the constructor.
Since hidden form is not related to the context, you need to exit them both at some time.
I eventually realized that the thread.IsBackground = true; was not bad, because it was the only way to determine "hey, I'm the last thread running, I'm supposed to quit". Correct resource cleaning is still needed, tough. For this, a third delegate for resource cleaning is needed and I just registered it to the AppDomain.CurrentDomain.ProcessExit event. I even provided a ExitLoop() method to the MessageLoop class (was MessagePump in the question). In this way, I can terminate the message loop anytime. Critical sections of ExitLoop() and ProcessExit handler are mutexed.
The code:
using System;
using System.Collections.Generic;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace System
{
public class MessageLoop
{
#region Fields
private Object _Lock;
private ApplicationContext _ApplicationContext;
private CustomMessageFilter _MessageFilter;
private HandleProvider _ResourceCleaner;
private ManualResetEvent _Completion;
private bool _Disposed;
#endregion // Fields
#region Constructors
/// <summary>
/// Run a second message pump that will filter messages asyncronously
/// </summary>
/// <param name="provideHandle">A delegate that provide a window handle for
/// resource initializing</param>
/// <param name="messageFilter">A delegate for message filtering</param>
/// <param name="cleanResources">A delegate for proper resource cleaning
/// before quitting the loop</param>
/// <param name="background">State if the loop should be run on a background
/// thread or not. If background = false, please be aware of the
/// possible race conditions on application shut-down.</param>
public MessageLoop(HandleProvider initializeResources, MessageFilter messageFilter,
HandleProvider cleanResources, bool background)
{
_Lock = new Object();
_ResourceCleaner = cleanResources;
_Completion = new ManualResetEvent(false);
_Disposed = false;
Thread thread = new Thread(delegate()
{
_ApplicationContext = new ApplicationContext();
WindowHandle window = new WindowHandle();
initializeResources(window.Handle);
_MessageFilter = new CustomMessageFilter(messageFilter);
Application.AddMessageFilter(_MessageFilter);
// Signal resources initalizated
_Completion.Set();
// If background = true, do resource cleaning on ProcessExit event
if (background)
{
AppDomain.CurrentDomain.ProcessExit +=
new EventHandler(CurrentDomain_ProcessExit);
}
// Run the message loop
Application.Run(_ApplicationContext);
// Clean resource before leaving the thread
cleanResources(window.Handle);
// Signal resources cleaned
_Completion.Set();
});
thread.SetApartmentState(ApartmentState.STA);
thread.IsBackground = background;
thread.Start();
// Before returning the instace, wait for thread resources initialization
_Completion.WaitOne();
}
#endregion // Constructors
#region Inquiry
/// <summary>
/// Early exit the message loop
/// </summary>
public void ExitLoop()
{
lock (_Lock)
{
if (_Disposed)
return;
// Completion was already signaled in the constructor
_Completion.Reset();
// Tell the message loop thread to quit
_ApplicationContext.ExitThread();
// Wait for thread resources cleaning
_Completion.WaitOne();
_Disposed = true;
}
}
#endregion // Inquiry
#region Event handlers
void CurrentDomain_ProcessExit(object sender, EventArgs e)
{
lock (_Lock)
{
if (_Disposed)
return;
// Completion was already signaled in the constructor
_Completion.Reset();
// Tell the message loop thread to quit
_ApplicationContext.ExitThread();
// Wait for thread resources cleaning
_Completion.WaitOne();
_Disposed = true;
}
}
#endregion // Event handlers
#region Support
public delegate void HandleProvider(IntPtr handle);
public delegate bool MessageFilter(ref Message m);
internal class CustomMessageFilter : IMessageFilter
{
private MessageFilter _Filter;
public CustomMessageFilter(MessageFilter filter)
{
_Filter = filter;
}
#region IMessageFilter Members
public bool PreFilterMessage(ref Message m)
{
return _Filter(ref m);
}
#endregion // IMessageFilter Members
}
#endregion // Support
}
public class WindowHandle : NativeWindow
{
public WindowHandle()
{
CreateParams parms = new CreateParams();
CreateHandle(parms);
}
~WindowHandle()
{
DestroyHandle();
}
}
}
Can be used this way:
_Completion = new ManualResetEvent(false);
MessageLoop messageLoop = new MessageLoop(
delegate(IntPtr handle) // Resource initializing
{
// Sample code, I did this form twain drivers low level wrapping
_Scanner = new TwainSM(handle);
_Scanner.LoadDs("EPSON Perfection V30/V300");
},
delegate(ref Message m) // Message filtering
{
// Asyncrhronous processing of the messages
// When the correct message is found -->
_Completion.Set();
},
delegate(IntPtr handle) // Resource cleaning
{
// Resource cleaning/disposing. In my case, it's the following...
_Scanner.Dispose();
}, true); // Automatically quit on main application shut-down
// Anytime you can exit the loop
messageLoop.ExitLoop();