Events in Multi Threaded Environment - c#

I'm trying to build a system via which users can build a little test program without knowing how to code. For that purpose I designed the system that way, that there is a procedure, which can contain other procedures or steps. The steps can contain commands. The procedure contains the logic what happens in which order. The steps contain the information to which step they are connected next.
The steps and commands are called by Execute and invoke OnDone if they are finished which may happen directly (eg IncreaseCommand) or after a certain period of time (WaitCommand or any other command communicating with the connected hardware; both are on a different thread). Additionally they can be stopped by a timeout or by user.
As long as there is no timeout everything works fine. If there is a timeout I tried to make the code thread safe by locking. Additionally there are these pitfalls when the timeout stops a command (eg WaitCommand) which is finishing its work in the same moment. So there is one thread working its way from the procedure via the step to the command, signalling stop and another thread working from the command via the step to the procedure signalling done.
I added some code snippets, which I've stripped of most of the dispose code and other internal stuff, which seems not to be related to the issue.
public sealed class Procedure : IStep, IStoppable
{
public event EventHandler Done;
public event EventHandler Stopped;
public event EventHandler TimedOut;
public void Run()
{
if (!IsRunning)
{
CheckStartTimer();
Start(First);
}
}
private void CheckStartTimer()
{
isTimerUnlinked = false;
timer.Elapsed += OnTimedOut;
timer.IntervalInMilliseconds = (int)Timeout.TotalMilliseconds;
timer.Start();
}
private void OnTimedOut(object sender, EventArgs e)
{
if (isTimerUnlinked)
return;
stopFromTimeout = true;
Stop();
}
private void Start(IStep step)
{
isStopped = false;
isStopping = false;
Active = step;
LinkActive();
active.Run();
}
private void LinkActive()
{
active.Done += OnActiveFinished;
if (active is Procedure proc)
proc.TimedOut += OnActiveFinished;
}
private void OnActiveFinished(object sender, EventArgs e)
{
UnlinkActive();
lock (myLock)
{
if (isStopped)
return;
if (stopFromTimeout)
{
OnStopped();
return;
}
}
var successor = active.ActiveSuccessor;
if (successor == null)
OnDone();
else if (isStopping || timeoutPending || stopFromTimeout)
OnStopped();
else
Start(successor);
}
public void Stop()
{
if (isStopping)
return;
isStopping = true;
StopTimer();
if (active is IStoppable stoppable)
{
stoppable.Stopped += stoppable_Stopped;
stoppable.Stop();
}
else
OnStopped();
}
private void stoppable_Stopped(object sender, EventArgs e)
{
var stoppable = sender as IStoppable;
stoppable.Stopped -= stoppable_Stopped;
OnStopped();
}
private void OnStopped()
{
isStopping = false;
lock (myLock)
{
isStopped = true;
}
UnlinkActive();
lock (myLock)
{
Active = null;
}
if (stopFromTimeout || timeoutPending)
{
stopFromTimeout = false;
timeoutPending = false;
CleanUp();
TimedOut?.Invoke(this, EventArgs.Empty);
}
else
Stopped?.Invoke(this, EventArgs.Empty);
}
private void UnlinkActive()
{
if (stopFromTimeout && !isStopped)
return;
lock (myLock)
{
if (active == null)
return;
active.Done -= OnActiveFinished;
var step = active as IStep;
if (step is Procedure proc)
proc.TimedOut -= OnActiveFinished;
}
}
private void OnDone()
{
CleanUp();
Done?.Invoke(this, EventArgs.Empty);
}
private void CleanUp()
{
Reset();
SetActiveSuccessor();
}
private void Reset()
{
Active = null;
stopFromTimeout = false;
timeoutPending = false;
StopTimer();
}
private void StopTimer()
{
if (timer == null)
return;
isTimerUnlinked = true;
timer.Elapsed -= OnTimedOut;
timer.Stop();
}
private void SetActiveSuccessor()
{
ActiveSuccessor = links[(int)Successor.Simple_If];
}
}
internal sealed class CommandStep : IStep, IStoppable
{
public event EventHandler Done;
public event EventHandler Started;
public event EventHandler Stopped;
public CommandStep(ICommand command)
{
this.command = command;
}
public void Run()
{
lock (myLock)
{
stopCalled = false;
if (cookie != null && !cookie.Signalled)
throw new InvalidOperationException(ToString() + " is already active.");
cookie = new CommandStepCookie();
}
command.Done += OnExit;
unlinked = false;
if (stopCalled)
return;
command.Execute();
}
public void Stop()
{
stopCalled = true;
if (command is IStoppable stoppable)
stoppable.Stop();
else
OnExit(null, new CommandEventArgs(ExitReason.Stopped));
}
private void OnExit(object sender, CommandEventArgs e)
{
(sender as ICommand).Done -= OnExit;
lock (myLock)
{
if (cookie.Signalled)
return;
cookie.ExitReason = stopCalled ? ExitReason.Stopped : e.ExitReason;
switch (cookie.ExitReason)
{
case ExitReason.Done:
default:
if (unlinked)
return;
Unlink();
ActiveSuccessor = links[(int)Successor.Simple_If];
break;
case ExitReason.Stopped:
Unlink();
break;
case ExitReason.Error:
throw new NotImplementedException();
}
cookie.Signalled = true;
}
if (cookie.ExitReason.HasValue)
{
active = false;
if (cookie.ExitReason == ExitReason.Done)
Done?.Invoke(this, EventArgs.Empty);
else if (cookie.ExitReason == ExitReason.Stopped)
stopCalled = false;
Stopped?.Invoke(this, EventArgs.Empty);
}
}
private void Unlink()
{
if (command != null)
command.Done -= OnExit;
unlinked = true;
}
}
internal sealed class WaitCommand : ICommand, IStoppable
{
public event EventHandler<CommandEventArgs> Done;
public event EventHandler Stopped;
internal WaitCommand(ITimer timer)
{
this.timer = timer;
timer.AutoRestart = false;
TimeSpan = TimeSpan.FromMinutes(1);
}
public void Execute()
{
lock (myLock)
{
cookie = new WaitCommandCookie(
e => Done?.Invoke(this, new CommandEventArgs(e)));
timer.IntervalInMilliseconds = (int)TimeSpan.TotalMilliseconds;
timer.Elapsed += OnElapsed;
}
timer.Start();
}
private void OnElapsed(object sender, EventArgs e)
{
OnExit(ExitReason.Done);
}
public void Stop()
{
if (cookie == null)
{
Done?.Invoke(this, new CommandEventArgs(ExitReason.Stopped));
return;
}
cookie.Stopping = true;
lock (myLock)
{
StopTimer();
}
OnExit(ExitReason.Stopped);
}
private void OnExit(ExitReason exitReason)
{
if (cookie == null)
return;
lock (myLock)
{
if (cookie.Signalled)
return;
Unlink();
if (cookie.Stopping && exitReason != ExitReason.Stopped)
return;
cookie.Stopping = false;
}
cookie.Signal(exitReason);
cookie = null;
}
private void StopTimer()
{
Unlink();
timer.Stop();
}
private void Unlink()
{
timer.Elapsed -= OnElapsed;
}
}
I've been testing at certain places whether a stop is ongoing and trying to intercept the done, so that it is not executed after the stop and cause any trouble. This way doesn't seem to be fully waterproof, althoug it seems to work for the moment. Is there a way to provide this kind of safety by design? Am I maybe doing this completely wrong?

Your shared state is not protected from concurrent access consistently. For example take the isStopped field:
private void OnStopped()
{
isStopping = false;
lock (myLock)
{
isStopped = true;
}
//...
private void Start(IStep step)
{
isStopped = false;
//...
In the first place it's protected, in the second it's not. You can either choose to protect it everywhere, or nowhere. There is no middle ground. Protecting it partially is as good as not protecting it at all.
As a side note, invoking event handlers while holding a lock is not recommended. An event handler could contain long running code, or call arbitrary code that is possibly protected by other locks, opening the possibility for deadlocks. The general advice about locking is to release it as quickly as possible. The longer you hold a lock, the more contention you create between threads. So for example in the method OnActiveFinished:
lock (myLock)
{
if (isStopped)
return;
if (stopFromTimeout)
{
OnStopped();
return;
}
}
You call the OnStopped while holding the lock. And inside the OnStopped you invoke the handlers:
Stopped?.Invoke(this, EventArgs.Empty);
The correct way is to call the OnStopped after releasing the lock. Use a local variable for storing the info about calling it or not:
var localInvokeOnStopped = false;
lock (myLock)
{
if (isStopped)
return;
if (stopFromTimeout)
{
localInvokeOnStopped = true;
}
}
if (localInvokeOnStopped)
{
OnStopped();
return;
}
Last advice, avoid locking recursively on the same lock. The lock statement will not complain if you do it (because the underlying Monitor class allows reentrancy), but it makes your program less understandable and maintainable.

Related

Restart InActivity Monitor after timer is stopped

I have WinForms App where I am using the code in this following Post to check the InActivity Status of my app (Please see the accepted answer in the post). InActivity In WinForms. Once the app reaches inactivity its stopping the inactivity monitor. But then I want to restart the time once the user logs in.
So I have a notification mechanism when the user logs in and I am calling the start timer method again. I get the Started Monitor Message but the app never tracks inactivity and I don't get Timer reporting app is InACTIVE message at all. Please help.
public static System.Windows.Forms.Timer IdleTimer =null;
static int MilliSeconds = 60000;
static void Main(string[] args)
{
f = new GeneStudyForm(true, arguments.SystemTimeOutFolder, arguments.SystemTimeOutFile, StartInActivityMonitor);
int x = StartInActivityMonitor();
}
public static void StartInActivityMonitor()
{
IdleTimer = new Timer();
LeaveIdleMessageFilter limf = new LeaveIdleMessageFilter();
Application.AddMessageFilter(limf);
IdleTimer.Interval = MilliSeconds; //One minute; change as needed
Application.Idle += new EventHandler(Application_Idle);
if (IdleTimer != null)
{
MessageBox.Show(IdleTimer.Interval.ToString());
}
IdleTimer.Tick += TimeDone;
IdleTimer.Tag = InActivityTimer.Started;
MessageBox.Show("starting");
IdleTimer.Start();
}
static private void Application_Idle(object sender, EventArgs e)
{
if (!IdleTimer.Enabled) // not yet idling?
IdleTimer.Start();
}
static private void TimeDone(object sender, EventArgs e)
{
try
{
MessageBox.Show("Stopped");
IdleTimer.Stop(); // not really necessary
f.MonitorDirectory();
f.UpdateInActivityStatus();
IdleTimer.Tick -= TimeDone;
Application.Idle -= new EventHandler(Application_Idle);
}
catch(Exception ex)
{
MessageBox.Show(ex.InnerException + ex.Data.ToString());
}
}
Here is my GeneStudyForm
public partial class GeneStudyForm
{
GeneStudySystemTimeOutIO GeneStudyIO;
Func<int> StartTimer;
//Passing the StartInActivityMonitor Method as Func Delegate
public GeneStudyForm(bool isStandalone, string TimeOutFolder, string TimeOutFile, System.Func<int> MyMethod)
{
GeneStudyIO = GeneStudySystemTimeOutIO.GetInstance(TimeOutFolder, TimeOutFile);
UpdateActivityStatus(AppName.GeneStudyStatus, ActivityStatus.Active);
this.StartTimer = MyMethod;
}
public void UpdateActivityStatus(AppName name, ActivityStatus status)
{
if (GeneStudyIO != null)
{
GeneStudyIO.WriteToFile(name, status);
}
}
public void MonitorDirectory()
{
FileSystemWatcher fileSystemWatcher = new FileSystemWatcher(GeneStudyIO.GetDriectory());
fileSystemWatcher.NotifyFilter = NotifyFilters.LastWrite;
fileSystemWatcher.Filter = "*.json";
fileSystemWatcher.Changed += FileSystemWatcher_Changed;
fileSystemWatcher.EnableRaisingEvents = true;
}
public void UnRegister(FileSystemWatcher fileSystemWatcher)
{
fileSystemWatcher.Changed -= FileSystemWatcher_Changed;
}
// I am writing the inactive status to a file. So this event will fill
private void FileSystemWatcher_Changed(object sender, FileSystemEventArgs e)
{
try
{
var root = GeneStudyIO.GetDesrializedJson();
if (root != null && root.AllApplications != null)
{
var item = root.AllApplications.Any(x => x.Status == ActivityStatus.Active.ToString());
if (!item)
{
if (InActivecount == 0)
{
GeneStudyAndApplicationCommon.TimeStatus = InActivityTimer.Ended;
MessageBox.Show("I am hiding");
this.Hide();
InActivecount++;
}
}
else
{
if (GeneStudyAndApplicationCommon.TimeStatus == InActivityTimer.Ended)
{
MessageBox.Show("I am showing");
this.Show();
UnRegister(sender as FileSystemWatcher);
UpdateActivityStatus(AppName.GeneStudyStatus, ActivityStatus.Active);
MessageBox.Show("Updated Status");
if (StartTimer != null)
{
MessageBox.Show("Starting Timer again");
if (StartTimer() == -1)
{
MessageBox.Show("Couldn't start timer");
}
}
}
}
}
}
catch (Exception ex)
{
SystemDebugLogLogger.LogException(ex);
}
}
}
This soulution is quite different from what I have posted. But I could solve my problem with this. But I want to post it if it helps someone. Here is the post I am following Last User Input
I created a class called IdleCheck where I am getting LastUserInput as follows
public static class IdleCheck
{
[StructLayout(LayoutKind.Sequential)]
private struct LASTINPUTINFO
{
[MarshalAs(UnmanagedType.U4)]
public int cbSize;
[MarshalAs(UnmanagedType.U4)]
public int dwTime;
}
[DllImport("user32.dll")]
private static extern bool GetLastInputInfo(ref LASTINPUTINFO x);
public static int GetLastInputTime()
{
var inf = new LASTINPUTINFO();
inf.cbSize = Marshal.SizeOf(inf);
inf.dwTime = 0;
return (GetLastInputInfo(ref inf)) ? Environment.TickCount - inf.dwTime : 0;
}
}
Next in the actual Form this is my code. I am using a simple yes no message box to see if the timer can be stopped and recalled again when needed. You can apply your own locking mechanism.
I want the app to time out if it is InActive for 20 seconds. Change it as needed.
public partial class Form1 : Form
{
Timer timer;
const int TIMEOUT_DONE = 20000;
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
Reset();
}
void timer_Tick(object sender, EventArgs e)
{
//var ms = TIMEOUT_DONE - IdleCheck.GetLastInputTime();
if (IdleCheck.GetLastInputTime() > TIMEOUT_DONE)
{
DialogResult dialogResult = MessageBox.Show("Sure", "Some Title", MessageBoxButtons.YesNo);
if (dialogResult == DialogResult.Yes)
{
Stop();
Reset();
}
}
}
public void Reset()
{
timer = new Timer();
timer.Interval = 10000;
timer.Tick += timer_Tick;
timer.Start();
}
public void Stop()
{
timer.Tick -= timer_Tick;
timer.Stop();
}
}

Background worker c# stop worker - immediately (include Thread.Sleep in DoWork)

i want to just stop my backgroundworker when i press a button :
Code looking like :
Button :
private void button6_Click(object sender, EventArgs e)
{
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
if (isOn == true)
{
isOn = false;
if (!backgroundWorker1.IsBusy)
{
backgroundWorker1.RunWorkerAsync();
this.button6.ForeColor = System.Drawing.Color.Lime;
}
}
else
{
isOn = true;
this.button6.ForeColor = System.Drawing.Color.Red;
backgroundWorker1.CancelAsync();
//////backgroundWorker1.Dispose();
}
And my Backgroundworker_DoWork look like :
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
if (backgroundWorker1.CancellationPending && backgroundWorker1.IsBusy)
{
e.Cancel = true;
return;
}
while (true)
{
if (backgroundWorker1.CancellationPending && backgroundWorker1.IsBusy)
{
e.Cancel = true;
break;
}
backgroundWorker1.Dispose();
click_na_default(hwnd1);
click_F8(hwnd1);
click_na_YELLS(hwnd1);
click_ENTER(hwnd1);
Thread.Sleep(100);
click_na_trade(hwnd1);
Thread.Sleep(100);
click_F8(hwnd1);
click_ENTER(hwnd1);
Thread.Sleep(100);
click_na_default(hwnd1);
Thread.Sleep(4000);
}
if (((BackgroundWorker)sender).CancellationPending)
{
e.Cancel = true;
//set this code at the end of file processing
return;
}
}
And the problem is : I can't .CancelAsync(); just immediately after button press again . My code just DoWork untill just Thread.Sleep(4000); is over.
When i press my button to stop work this gonna stop just after end while loop.
I know i can add
if (backgroundWorker1.CancellationPending && backgroundWorker1.IsBusy)
{
e.Cancel = true;
return;
}
After everyline in my Backgroundworker_DoWork but it's so stupid and when i get Thread.Sleep(10000); it gonna takes 10 sec...
Is any way to just kill instantly my background worker?
Thanks for help!
I think that standard BackgroundWorker is not suitable for your case and you should do something custom that better support combination of sleep and cancellation. Following code is an idea of what you might want to do:
CancellableBackgroundWorker.cs
This is a class similar to standard BackgroundWorker but providing some callbacks for your goal (see ICancellationProvider and FinishedEvent).
public delegate void CancellableBackgroundJob(ICancellationProvider cancellation);
public interface ICancellationProvider
{
bool CheckForCancel();
void CheckForCancelAndBreak();
void SleepWithCancel(int millis);
}
public class CancellableBackgroundWorker : Component, ICancellationProvider
{
private readonly ManualResetEvent _canceledEvent = new ManualResetEvent(false);
private readonly CancellableBackgroundJob _backgroundJob;
private volatile Thread _thread;
private volatile bool _disposed;
public EventHandler FinishedEvent;
public CancellableBackgroundWorker(CancellableBackgroundJob backgroundJob)
{
_backgroundJob = backgroundJob;
}
protected override void Dispose(bool disposing)
{
Cancel();
_disposed = true;
}
private void AssertNotDisposed()
{
if (_disposed)
throw new InvalidOperationException("Worker is already disposed");
}
public bool IsBusy
{
get { return (_thread != null); }
}
public void Start()
{
AssertNotDisposed();
if (_thread != null)
throw new InvalidOperationException("Worker is already started");
_thread = new Thread(DoWorkWrapper);
_thread.Start();
}
public void Cancel()
{
AssertNotDisposed();
_canceledEvent.Set();
}
private void DoWorkWrapper()
{
_canceledEvent.Reset();
try
{
_backgroundJob(this);
Debug.WriteLine("Worker thread completed successfully");
}
catch (ThreadAbortException ex)
{
Debug.WriteLine("Worker thread was aborted");
Thread.ResetAbort();
}
finally
{
_canceledEvent.Reset();
_thread = null;
EventHandler finished = FinishedEvent;
if (finished != null)
finished(this, EventArgs.Empty);
}
}
#region ICancellationProvider
// use explicit implementation of the interface to separate interfaces
// I'm too lazy to create additional class
bool ICancellationProvider.CheckForCancel()
{
return _canceledEvent.WaitOne(0);
}
void ICancellationProvider.CheckForCancelAndBreak()
{
if (((ICancellationProvider)this).CheckForCancel())
{
Debug.WriteLine("Cancel event is set, aborting the worker thread");
_thread.Abort();
}
}
void ICancellationProvider.SleepWithCancel(int millis)
{
if (_canceledEvent.WaitOne(millis))
{
Debug.WriteLine("Sleep aborted by cancel event, aborting the worker thread");
_thread.Abort();
}
}
#endregion
}
The main trick is to use ManualResetEvent.WaitOne instead of Thread.Sleep for sleeping. With such approach working thread might be safely woken up (for cancellation) from a different (UI) thread. Another trick is to use ThreadAbortException via Thread.Abort to enforce quick end of the background thread execution (and don't forget about Thread.ResetAbort at the end of stack unwinding).
You may use this class as following:
public partial class Form1 : Form
{
private readonly CancellableBackgroundWorker _backgroundWorker;
public Form1()
{
InitializeComponent();
_backgroundWorker = new CancellableBackgroundWorker(DoBackgroundJob);
_backgroundWorker.FinishedEvent += (s, e) => UpdateButton();
// ensure this.components is created either by InitializeComponent or by us explicitly
// so we can add _backgroundWorker to it for disposal
if (this.components == null)
this.components = new System.ComponentModel.Container();
components.Add(_backgroundWorker);
}
private void UpdateButton()
{
// Ensure we interact with UI on the main thread
if (InvokeRequired)
{
Invoke((Action)UpdateButton);
return;
}
button1.Text = _backgroundWorker.IsBusy ? "Cancel" : "Start";
}
private void button1_Click(object sender, EventArgs e)
{
if (_backgroundWorker.IsBusy)
{
_backgroundWorker.Cancel();
}
else
{
_backgroundWorker.Start();
}
UpdateButton();
}
private void DoBackgroundJob(ICancellationProvider cancellation)
{
Debug.WriteLine("Do something");
// if canceled, stop immediately
cancellation.CheckForCancelAndBreak();
Debug.WriteLine("Do something more");
if (cancellation.CheckForCancel())
{
// you noticed cancellation but still need to finish something
Debug.WriteLine("Do some necessary clean up");
return;
}
// Sleep but cancel will stop and break
cancellation.SleepWithCancel(10000);
Debug.WriteLine("Last bit of work");
}
}

variable not set by background worker on first pass

I have an observer with a background worker. Lets say the observer has the following structure:
internal class Observer
{
private readonly BackgroundWorker bw1;
internal Object target;
public Observer()
{
bw1 = new BackgroundWorker();
bw1.DoWork += bw1_DoWork;
bw1.RunWorkerCompleted += bw1_RunWorkerCompleted;
bw1.WorkerSupportsCancellation = true;
}
private void bw1_DoWork(object sender, DoWorkEventArgs e)
{
e.Result = new object(); // Query to database
}
private void bw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
target = e.Result as object;
if (ChangedScannedValue != null)
{
ChangedScannedValue(_scannedValue);
}
}
private String _scannedValue = string.Empty;
internal delegate void OnChangedScannedValue(String scannedValue);
internal event OnChangedScannedValue ChangedScannedValue;
internal String ScannedValue
{
get { return _scannedValue; }
set
{
_scannedValue = value;
bw1.RunWorkerAsync(_scannedValue);
//ProcessScannedValue();
}
}
}
I have another class listening to the event.
public partial class myControl : UserControl
{
Observer _observer = new Observer();
public myControl()
{
InitializeComponent();
}
internal void LoadData(Observer observer)
{
_observer = observer;
_observer.ChangedScannedValue += _observer_ChangedScannedValue;
}
void _observer_ChangedScannedValue(string ScannedValue)
{
if (_observer.target != null)
{
// Do Stuff
}
else
{
MessageBox.Show("NO TARGET FOUND.");
}
}
}
The thing is. initially, after the background worker finishes, I get the message box "NO TARGET FOUND.", however immediatley after, it would //Do Stuff Debugging shows that the RunWorkerCompleted event fires twice. This only happens on the first change to scanned value, all changes afterwards work as desired.
Questions:
1) Why does RunWorkerCompleted fire twice?
2) Why is the target not updated on the first fire of RunWorkerCompleted
You could try again with target being set in bw1_DoWork already, i.e.:
private void bw1_DoWork(object sender, DoWorkEventArgs e) {
target = new object(); // Query to database
}
private void bw1_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e) {
if (ChangedScannedValue != null) {
ChangedScannedValue(_scannedValue);
}
}
Eventually you may want to replace the BackgroundWorker by a simpler solution with ThreadPool. I'd suggest this:
internal String ScannedValue {
get { return _scannedValue; }
set {
_scannedValue = value;
ThreadPool.QueueUserWorkItem( (WaitCallback) delegate {
target = new object(); // query database
if (ChangedScannedValue != null) ChangedScannedValue(_scannedValue);
} );
}
}

C# Producer/Consumer implementation issue

I have a producer-consumer class as following.
public class ProducerConsumer<T> where T : class
{
private Thread _workerThread;
private readonly Queue<T> _workQueue;
private readonly object _lockObject = new object();
private readonly Action<T> _workCallbackAction;
private ManualResetEvent _workerWaitSignal;
public ProducerConsumer(Action<T> action)
{
_workCallbackAction = action;
_workQueue = new Queue<T>();
}
private void DoWork()
{
while (true)
{
T workItemToBeProcessed = default(T);
bool hasSomeWorkItem = false;
lock (_lockObject)
{
hasSomeWorkItem = _workQueue.Count > 0;
if (hasSomeWorkItem)
{
workItemToBeProcessed = _workQueue.Dequeue();
if (workItemToBeProcessed == null)
{
return;
}
}
}
if (hasSomeWorkItem)
{
if (_workCallbackAction != null)
{
_workCallbackAction(workItemToBeProcessed);
}
}
else
{
_workerWaitSignal.WaitOne();
Debug.WriteLine("Waiting for signal.");
}
}
}
public void EnQueueWorkItem(T workItem)
{
lock (_lockObject)
{
_workQueue.Enqueue(workItem);
_workerWaitSignal.Set();
}
}
public void StopWork(ManualResetEvent stopSignal)
{
EnQueueWorkItem(null);
_workerThread.Join();
_workerWaitSignal.Close();
_workerWaitSignal = null;
if (stopSignal != null)
{
stopSignal.Set();
}
}
public void ReStart()
{
_workerWaitSignal = new ManualResetEvent(false);
_workerThread = new Thread(DoWork) { IsBackground = true };
_workerThread.Start();
}
}
and i am using this in the following way:
public partial class Form1 : Form
{
private RecordProducerConsumer<string> _proConsumer;
public Form1()
{
InitializeComponent();
_proConsumer = new RecordProducerConsumer<string>(DoAction);
}
private bool restart=true;
private int item = 0;
private void button1_Click(object sender, EventArgs e)
{
if (restart)
{
_proConsumer.ReStart();
restart = false;
}
item++;
_proConsumer.EnQueueWorkItem(item.ToString());
}
private void DoAction(string str)
{
Debug.WriteLine(str);
}
private void btnStop_Click(object sender, EventArgs e)
{
ManualResetEvent mre = new ManualResetEvent(false);
_proConsumer.StopWork(mre);
mre.WaitOne();
restart = true;
}
private void Stop(ManualResetEvent mre)
{
mre.WaitOne();
}
}
My problem or what I can not understand is when I click Start button I am adding only one item and it Dequeue that item but keeps on running the loop so I see lot of "Waiting for signal." print outs on the Output window in Visual Studio.
Why does it not stop on _workerWaitSignal.WaitOne(); in DoWork() method , why is it running all the time ?
Couple of issues:
It makes hardly any sense to output 'Waiting for issue' after executing the wait. Consider moving the write before the actual wait.
You are using a ManualResetEvent — as its name indicates, it requires a manual reset to revert from the signalled state. However, I can't see a call to Reset in your code.
To avoid other concurrency issues (e.g. a race condition when when resetting the event while other thread set the event after enqueuing another work item), consider using a Semaphore for your scenario instead.
try this... I could be wrong...but that is all i could figure out by reading through your code. Hope this helps :)
private void button1_Click(object sender, EventArgs e)
{
if (restart)
{
restart = false;
_proConsumer.ReStart();
}
item++;
_proConsumer.EnQueueWorkItem(item.ToString());
}
I haven't read the code thoroughly, but I can venture a guess that you meant to use an AutoResetEvent (which resets automatically after some WaitOne() is released) rather than a ManualResetEvent (which stays set until you explicitly call Reset()).
Also, is there any reason you're not using .NET's BlockingCollection<T> ? It's the framework implementation of the producer/consumer pattern, and it works very well.

C#, how to avoid recursion elegantly?

I've got an event-function, where the code inside the function may trigger calling the same function again (because a message loop is used). How can I avoid this or better "detect" this most elegantly? When it comes to multiple thread synchronization one could write:
public void Closing(object sender, EventArgs e)
{
lock(m_Object)
{
<Code, which can trigger Closing again>
}
}
But in my case it is the same thread that may call the same function and thus lock fails to work.
Introduce a private bool m_IsClosing variable that indicates whether closing is already in progress.
public void Closing(object sender, EventArgs e)
{
lock (m_Object)
{
if (m_IsClosing)
return;
m_IsClosing = true;
try
{
// Code, which can trigger Closing again
}
finally
{
m_IsClosing = false;
}
}
}
I don't think it's particularly elegant, but without knowing more about what you're doing, I can't any better solution.
Edit: adapted example for possible exceptions while closing.
Add a member of your class closingInProgress initialized to false. Then:
public void Closing(object sender, EventArgs e)
{
if (closingInProgress)
{
return;
}
try
{
closingInProgress = true;
<Code, which can trigger Closing again>
}
finally
{
closingInProgress = false;
}
}
Use a private field to remember whether Closing has already been called. If it has, then abort from the procedure:
private bool ClosingDone;
public void Closing(object sender, EventArgs e)
{
if (!ClosingDone)
{
ClosingDone = true;
// Code, which can trigger Closing again
}
}
A simple solution is to have a flag available in your class which indicates the event is being handled. Using this flag, you can determine whether to run the method again when called recursively.
private bool m_IsClosing;
public void Closing(object sender, EventArgs e)
{
lock(m_Object)
{
// Check for state.
if(m_IsClosing)
return;
m_IsClosing = true;
try
{
// The rest of your code.
}
finally
{
m_IsClosing = false;
}
}
}
The only difficulty will be ensuring you don't read or modify this value elsewhere without entering a critical section locked on m_Object. Otherwise, it should prevent the event handler doing any work recursively.
It's a bit old, but how about that:
public class Locker : IDisposable
{
bool _isActive;
public bool IsActive
{
get { return _isActive; }
}
private Locker()
{
_isActive = false;
}
public static Locker Create()
{
return new Locker();
}
public void Dispose()
{
_isActive = false;
}
public Locker Activate()
{
_isActive = true;
return this;
}
}
usage:
Locker lo = Locker.Create();
private void Foo()
{
if (lo.IsActive) return;
using(lo.Activate())
{
Console.WriteLine("Foo");
Bar();
}
}
private void Bar()
{
Console.WriteLine("Bar");
Foo();
}
Method Foo() is only called once. using makes sure, that lo is deactivated, even if an error occures.

Categories

Resources