I have a lot of ToggleSwitch and Combobox controls in my application, I am setting them up on application startup, which fires all the events associated with those controls.
These events are supposed to fire only when the user interacts with the controls and not when the value is changed programmatically.
Is there a way to disable all the events and reactivate them afterwards?
I didn't find any efficient solution on other posts or on the Internet.
As some have said, this smells of bad architecture and that you would even want to do this in the first place, but there is a way you can "block" events using an if statement and a simple boolean.
First, you'll want to declare a field in your class.
private bool _blockHandlers;
Once you've done that, you just set the bool to true where you want to start blocking handler, probably in your class constructor if its straight away. As I don't know the name of your class I won't go there.
But lets say you have an event handler
private void SomeHandler
{
if (blockHandlers)
{
return;
}
// It's not blocked, lets continue...
}
This way, you can temporarly block handlers or permenantly block them, with this method you just simple check if they're blocked, if not you can continue with your handler.
Disabling all events isn't possible because there are many event handlers in the framework that you have no direct control over. The fact you want to do this at all suggests that your design is flawed.
You can detect the difference between user interaction and programmatic change with a bit of plumbing and the diligence to use it everywhere that it matters. Suppose you have a ComboBox, and you want to detect when the user triggers SelectionChanged. This can be done with a flag, set only when you make programmatic changes. i.e.
private bool blockHandlers;
// Wrapped in a method for convenience.
public void SetSelectedIndex(int index)
{
blockHandlers = true;
comboBox.SelectedIndex = index;
blockHandlers = false;
}
private void ComboBox_SelectionChanged(object sender, SelectionChangedEventArgs e)
{
if (blockHandlers) return;
// Your event handling code...
}
Using this technique requires that you always either use SetSelectedIndex or set/reset blockHandlers around programmatic changes to ensure event handler(s) observe it and do nothing.
I come up with more elegant solution for this problem.
I created the following helper class:
public class WpfEventExecutor
{
bool isLoaded = false;
bool isProcessingEvent = false;
public void SetLoaded()
{
isLoaded = true;
}
public void Execute(Action action)
{
if (!isLoaded) return;
if (isProcessingEvent) return;
isProcessingEvent = true;
action();
isProcessingEvent = false;
}
}
GeneralView.xaml:
<Page x:Class="MyApp.UI.Settings.Views.GeneralView"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
xmlns:ui="http://schemas.modernwpf.com/2019"
xmlns:local="clr-namespace:MyApp.UI.Settings.Views"
mc:Ignorable="d"
Title="GeneralItem">
<StackPanel>
<ui:ToggleSwitch Name="StartWithWindowsCheckbox" Toggled="StartWithWindowsCheckbox_OnToggled" Header="Start with Windows" />
<Line Height="20" />
<TextBlock>Language</TextBlock>
<ComboBox Margin="0,10,0,0">
<ComboBoxItem Selected="EnglishBoxItem_OnSelected" Name="EnglishBoxItem">English</ComboBoxItem>
<ComboBoxItem Selected="ChineseBoxItem_OnSelected" Name="ChineseBoxItem">简体中文</ComboBoxItem>
</ComboBox>
</StackPanel>
</Page>
GeneralView.cs:
using System.Windows;
using MyApp.Helpers;
using Page = System.Windows.Controls.Page;
namespace MyApp.UI.Settings.Views
{
/// <summary>
/// Interaction logic for GeneralItem.xaml
/// </summary>
public partial class GeneralView : Page
{
readonly WpfEventExecutor eventExecutor = new WpfEventExecutor();
public GeneralView()
{
InitializeComponent();
StartWithWindowsCheckbox.IsOn = LogicHelpers.IsStartWithWindows();
switch (MyApp.Settings.Values.General.Language)
{
case MyApp.Settings.GeneralType.LanguageType.English:
EnglishBoxItem.IsSelected = true;
break;
case MyApp.Settings.GeneralType.LanguageType.Chinese:
ChineseBoxItem.IsSelected = true;
break;
}
// Call this method only after you finished to change the elements programmatically
// After calling to this method, we assume that each time the elements values changed,
// it caused by the user and not via code
eventExecutor.SetLoaded();
}
void StartWithWindowsCheckbox_OnToggled(object sender, RoutedEventArgs e)
{
eventExecutor.Execute(() =>
{
// This is safe section where to process the event.
// This way, the event will not call itself and we prevent stackoverflow error
// Your code is here
});
}
void EnglishBoxItem_OnSelected(object sender, RoutedEventArgs e)
{
eventExecutor.Execute(() =>
{
// This is safe section where to process the event.
// This way, the event will not call itself and we prevent stackoverflow error
// Your code is here
});
}
void ChineseBoxItem_OnSelected(object sender, RoutedEventArgs e)
{
eventExecutor.Execute(() =>
{
// This is safe section where to process the event.
// This way, the event will not call itself and we prevent stackoverflow error
// Your code is here
});
}
}
}
Related
Sorry for the title, i didn't find it easy to resume.
My issue is that I need to implement a c# dll that implements a 'scan' method, but this scan, when invoked, must not block the main thread of the application using the dll. Moreover, it is a duty that after the scan resolves it rises an Event.
So my issue (in the deep) is that i'm not so experienced at c#, and after very hard investigation i've come up with some solutions but i'm not very sure if they are the "right" procedures.
In the dll i've come up with:
public class Reader
{
public delegate void ReaderEventHandler(Object sender, AlertEventArgs e);
public void Scan(String ReaderName)
{
AlertEventArgs alertEventArgs = new AlertEventArgs();
alertEventArgs.uuiData = null;
//Code with blocking scan function here
if (ScanFinnished)
{
alertEventArgs.uuiData = "Scan Finnished!";
}
alertEventArgs.cardStateData = readerState[0].eventState;
ReaderEvent(new object(), alertEventArgs);
}
public event ReaderEventHandler ReaderEvent;
}
public class AlertEventArgs : EventArgs
{
#region AlertEventArgs Properties
private string _uui = null;
private uint cardState = 0;
#endregion
#region Get/Set Properties
public string uuiData
{
get { return _uui; }
set { _uui = value; }
}
public uint cardStateData
{
get { return cardState; }
set { cardState = value; }
}
#endregion
}
While in the main app I do:
Reader reader;
Task polling;
String SelectedReader = "Some_Reader";
private void bButton_Click(object sender, EventArgs e)
{
reader = new Reader();
reader.ReaderEvent += new Reader.ReaderEventHandler(reader_EventChanged);
polling = Task.Factory.StartNew(() => reader.Scan(SelectedReader));
}
void reader_EventChanged(object sender, AlertEventArgs e)
{
MessageBox.Show(e.uuiData + " Estado: " + e.cardStateData.ToString("X"));
reader.Dispose();
}
So here, it works fine but i don't know if it's the proper way, in addition i'm not able to handle possible Exceptions generated in the dll.
Also tried to use async/await but found it difficult and as I understand it's just a simpler workaround Tasks.
What are the inconvinients of this solution? how can i capture Exceptions (are they in other threads and that's why i cant try/catch them)? Possible concept faults?
When your class sends events, the sender usually is that class, this. Having new object() as sender makes absolutely no sense. Even null would be better but... just use this.
You shouldn't directly raise events as it might result in race conditions. Might not happen easily in your case but it's just a good guideline to follow. So instead of calling ReaderEvent(new object(), alertEventArgs); call RaiseReaderEvent(alertEventArgs); and create method for it.
For example:
private void RaiseReaderEvent(AlertEventArgs args)
{
var myEvent = ReaderEvent; // This prevents race conditions
if (myEvent != null) // remember to check that someone actually subscribes your event
myEvent(this, args); // Sender should be *this*, not some "new object()".
}
Though I personally like a bit more generic approach:
private void Raise<T>(EventHandler<T> oEvent, T args) where T : EventArgs
{
var eventInstance = oEvent;
if (eventInstance != null)
eventInstance(this, args);
}
Which can then be used to raise all events in same class like this:
Raise(ReaderEvent, alertEventArgs);
Since your scan should be non-blocking, you could use tasks, async/await or threads for example. You have chosen Tasks which is perfectly fine.
In every case you must understand that when you are not blocking your application, your application's main thread continues going like a train. Once you jump out of that train, you can't return. You probably should declare a new event "ErrorEvent" that is raised if your scan-procedure catches an exception. Your main application can then subscribe to that event as well, but you still must realize that those events are not (necessarily) coming from the main thread. When not, you won't be able to interact with your GUI directly (I'm assuming you have one due to button click handler). If you are using WinForms, you'll have to invoke all GUI changes when required.
So your UI-thread safe event handler should be something like this:
void reader_EventChanged(object sender, AlertEventArgs e)
{
if (InvokeRequired) // This true for others than UI Thread.
{
Invoke((MethodInvoker)delegate
{
Text = "My new title!";
});
}
else
Text = "My new title!";
}
In WPF there's Dispather that handles similar invoking.
I am writing a tool which switchs between a lot of states. For some events I need to be sure they wont get executed a second time while the called function (inside the event) is running. This is how I managed it before:
// Global variables //
public bool func1IsRunning = false;
public bool func2IsRunning = false;
...
public void listView_DoubleClick(object sender, EventArgs e)
{
if(!func1IsRunning)
{
func1();
func1IsRunning = false;
}
}
public void func1()
{
func1IsRunning = true;
// some code in here //
}
But with every extension of my tool the list of the global variables grows up. Also the events and functions getting less clear to read.
Isnt there a way like this(?):
public void listView_DoubleClick(object sender, EventArgs e)
{
if(DoubleClick.IsHandled)
{
func1();
}
}
public void func1()
{
// some code in here //
// ................. //
DoubleClick.IsHandled = true; // at the end of the function //
}
So what I am looking for is a way to determine if an event is still running or not. My code is working, im just unhappy with how it looks like.
Any ideas?
UPDATE 1
I decided to use Steve's answer as it solves my problem by the clearest way.
Anyway it is NOT running correctly for now.
Here is how my code looks like:
public void listView_DoubleClick(object sender, EventArgs e)
{
try
{
listView.DoubleClick -= new EventHandler(listView_DoubleClick);
itemEdit();
}
finally
{
listView.DoubleClick += new EventHandler(listView_DoubleClick);
}
}
The code above is NOT disabling the handler.
public void listView_DoubleClick(object sender, EventArgs e)
{
try
{
listView.DoubleClick -= listView_DoubleClick;
itemEdit();
}
finally
{
listView.DoubleClick += listView_DoubleClick;
}
}
This code is also not disabling the handler.
This is the line where the handler gets enabled (MainForm.Designer.cs):
this.listView.DoubleClick += new System.EventHandler(this.listView_DoubleClick);
There are no errors raised. The event just gets fired again and again. Where is the problem?
UPDATE 2:
As Sinatr asked in the comments below if my function is really waiting or just enabling user input he discovered where the mistake was made.
Steve's answer is correct according to my wrong written question. Thanks a lot to all of you guys.
Just disable the event handler
public void listView_DoubleClick(object sender, EventArgs e)
{
try
{
listView.DoubleClick -= listView_DoubleClick;
// Now, even if func1 causes a DoubleClick event,
// or user manages to trigger a DobuleClick
// there is no event registered and this code could
// reentered until you exit from func1.
func1();
}
finally
{
// Important part. the finally block is required
// because you should readd the event handler
// ALSO in case an exception occurs in func1
// and it is not handled there
listView.DoubleClick += listView_DoubleClick;
}
}
EDIT
Looking at your comment I suspect that this DoubleClick event is assigned to more than one control. If this is the case, using the global listView global instance of a listview doesn't disable the double click on other controls that are linked to the same code.
If this is the case then you need a more generic approach
public void listView_DoubleClick(object sender, EventArgs e)
{
Control c = sender as Control;
try
{
if(c != null)
{
c.DoubleClick -= listView_DoubleClick;
// Now, even if func1 causes a DoubleClick event,
// or user manages to trigger a DobuleClick
// there is no event registered and this code could
// reentered until you exit from func1.
func1();
}
}
finally
{
// Important part. the finally block is required
// because you should readd the event handler
// ALSO in case an exception occurs in func1
// and it is not handled there
if(c != null) c.DoubleClick += listView_DoubleClick;
}
}
Of course, this is just to enable/disable DoubleClicks events, it cannot works if you assign this event handler to other standard events like Click that have the same signature (object sender, EventArgs e)
How about something like the following using locks:
private object globalLock = new object();
private Dictionary<int, object> lockObjects = new Dictionary<int, object>();
public void listView_DoubleClick(object sender, EventArgs e)
{
object lockObject;
lock (globalLock) // to avoid two threads creating the object
{
if (!lockObjects.ContainsKey(1))
lockObjects.Add(1, new object());
lockObject = lockObjects[1];
}
if (Monitor.TryEnter(lockObject) // enter only if no thread has already entered
{
try { func1(); }
finally { Monitor.Exit(lockObject); }
}
}
This is different to Steve's logic in the matter that it is thread-safe.
A simple state-machine should solve your problem without requiring too many variables. Create an Enum named AppState like this:
enum AppState
{
Ready = 1,
InsideListView1Click = 2,
InsideListView1DoubleClick = 3
InsideListView2Click = 4,
InsideListView2DoubleClick = 5
}
This enum could grow as you add new controls and/or event-handlers to your application. Now use a single global variable that keeps track of the application state and modify it inside event-handlers appropriately:
private AppState m_State = AppState.Ready;
And in the event-handlers you would do:
private void ListView1_DoubleClick(object sender, EventArgs e)
{
lock
{
if(m_State != AppState.Ready)
return;
else
m_State = AppState.InsideListView1DoubleClick;
}
//Do your stuff
m_State = AppState.Ready;
}
This way newer calls will be ignored instead of being queued. If you expect to be in multiple states at the same time, you could apply [Flags] attribute on this enum as well. Also note that enums are thread-safe and evaluating them is atomic, so multi-threading shouldn't be a problem either.
Using Silverlight 5, RIA services 1.0 and Telerik controls I have a dialog with a button that when clicked goes and runs some service code.
The issue is when I double click or triple click it real fast, it keeps calling the service thus getting this error:
System.InvalidOperationException:
System.InvalidOperationException: A SubmitChanges operation is already in progress on this DomainContext.
I was wondering if this is a common error and any work around for it?
Here is the .NET source code that it goes to that causes it to crash:
public virtual SubmitOperation SubmitChanges(Action<SubmitOperation> callback, object userState)
{
if (this.IsSubmitting)
throw new InvalidOperationException(Resource.DomainContext_SubmitAlreadyInProgress);
This is not an error. A submitChange operation is asynchronous so you have to detect that it is completed before doing something else.
One solution could be to block the user from clicking on the button before the operation is completed.
Since you are using a Telerik controls, you can use a busy indicator.
private void btnUserAction_Click(object sender, RoutedEventArgs e)
{
myBusyIndicator.IsBusy = true;
// DO your stuff
var submitOperation = myContext.SubmitChanges();
submitOperation.Completed += (s, e) =>
{
// It is completed, now the user can click on the button again
myBusyIndicator.IsBusy = false;
}
}
EDIT : The busy indicator should be defined in your Xaml, like this :
<Telerik:RadBusyIndicator x:Name="myBusyIndicator">
<Grid x:Name="LayoutRoot" >
<Button Name="btnUserAction" Click="btnUserAction_Click" />
</Grid>
</Telerik:RadBusyIndicator>
I had this same problem. So I created a property on my ViewModel and bound it to my IsEnabled property of my button. I set this to false when the save starts and to true when it is done.
<Button Content="Save" IsEnabled="{Binding FinishedDataTransfer}" ...
bool _finishedDataTransfer = false;
public bool FinishedDataTransfer
{
get { return _finishedDataTransfer; }
set
{
_finishedDataTransfer = value;
RaisePropertyChanged("FinishedDataTransfer");
}
}
The advantage of doing it this way instead of a busy indicator is that the user can still do thing with the page while the save is running.
We had same problem, and had to change from sending a command to the viewmodel to having a bit of code behind for button click ...
/// <summary>
/// Workaround for handling double-click from the Map Results button - trying to prevent running the query twice and adding duplicate layers into the Layer Control
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void MapResultsButton_MouseLeftButtonDown(object sender, MouseButtonEventArgs e)
{
if (e.ClickCount == 1)
this.ViewModel.MapResultsCommand.Execute(null);
}
As you can see, this uses a ClickCount property which is part of the MouseButtonEventArgs
Once the command has reached the viewmodel we can then worry about busy indicators and other ways of disabling the button.
I'm implementing a visual version of Tracert (as a learning exercise) in WPF where results go to a listbox. The issues are (1) the listbox bound to tracertDataView is not updating, but (2) my entire application hangs.
I'm sure #2 is a threading issue but I'm not sure how to correct it (in the right way). In addition I'm not sure my technique of updating / binding the results of "DoTrace" are correct.
Here is my datasource in App.xaml
<Window.Resources>
<CollectionViewSource
Source="{Binding Source={x:Static Application.Current}, Path=TracertResultNodes}"
x:Key="tracertDataView" />
</Window.Resources>
App.xaml.cs
public partial class App : Application
{
private ObservableCollection<TracertNode> tracertResultNodes = new ObservableCollection<TracertNode>();
public void AppStartup(object sender, StartupEventArgs e)
{
// NOTE: Load sample data does work correctly.. and displays on the screen.
// subsequent updates do not display
LoadSampleData();
}
private void LoadSampleData()
{
TracertResultNodes = new ObservableCollection<TracertNode>();
TracertNode t = new TracertNode();
t.Address = new System.Net.IPAddress(0x2414188f);
t.RoundTripTime = 30;
t.Status = System.Net.NetworkInformation.IPStatus.BadRoute;
TracertResultNodes.Add(t);
}
public ObservableCollection<TracertNode> TracertResultNodes
{
get { return this.tracertResultNodes; }
set { this.tracertResultNodes = value; }
}
}
Here is the MainWindow code
public partial class MainWindow : Window
{
CollectionViewSource tracertDataView;
TraceWrapper _tracertWrapper = null;
public MainWindow()
{
InitializeComponent();
_tracertWrapper = new TraceWrapper();
tracertDataView = (CollectionViewSource)(this.Resources["tracertDataView"]);
}
private void DoTrace_Click(object sender, RoutedEventArgs e)
{
((App)Application.Current).TracertResultNodes = _tracertWrapper.Results;
_tracertWrapper.DoTrace("8.8.8.8", 30, 50);
}
}
FYI Internal implementation Detail of instance object "traceWrapper.DoTrace"
/// <summary>
/// Trace a host. Note that this object internally calls the Async implementation of .NET's PING.
// It works perfectly fine in a CMD host, but not in WPF
/// </summary>
public ObservableCollection<TracertNode> DoTrace(string HostOrIP, int maxHops, int TimeOut)
{
tracert = new Tracert();
// The following is triggered for every host that is found, or upon timeout
// (up to 30 times by default)
AutoResetEvent wait = new AutoResetEvent(false);
tracert.waiter = wait;
tracert.HostNameOrAddress = HostOrIP;
tracert.Trace();
this.Results = tracert.NodeList;
while (tracert.IsDone == false)
{
wait.WaitOne();
IsDone = tracert.IsDone;
}
return tracert.NodeList;
}
I don't understand how u used AutoResetEvent, i guess it is not supposed to be used in this way :)
But since Trace run already in another thread, are you sure there is not an event "OnTracertComplete" or something like that in your Tracert class?
If there is not, why you just don't put a DispatchTimer into your application?
That timer would periodically poll until tracert.IsDone becomes true.
If you block the execution of the application thread until an operation completes, you block the execution of the window event loop so window will never be updated.
Another important thing: you cannot update ObservableCollections from another thread.
Be careful and be sure that everything that is updated in the WPF window is executed from the same thread of the window. Don't know what your Trace class do exactly, but your problem here seems to be of course the wait loop, that don't makes sense in a GUI application.
Use notification events or a timer to poll the result. A timer with 1 second resolution seems good to me for this particular implementation and the performance inpact is absolutely minimal.
This is a possible implementation if you are able to modify the Tracert class.
public delegate void TracertCallbacHandler(Tracert sender, TracertNode newNode);
public class Tracert
{
public event TracertCallbacHandler NewNodeFound;
public event EventHandler TracertCompleted;
public void Trace()
{
....
}
// This function gets called in tracert thread\async method.
private void FunctionCalledInThreadWhenPingCompletes(TracertNode newNode)
{
var handler = this.NewNodeFound;
if (handler != null)
handler(this, newNode);
}
// This function gets called in tracert thread\async methods when everything ends.
private void FunctionCalledWhenEverythingDone()
{
var handler = this.TracertCompleted;
if (handler != null)
handler(this, EventArgs.Empty);
}
}
And here is the code to run the tracert,
This is TracertWrapper.
// Keep the observable collection as a field.
private ObservableCollection<TracertNode> pTracertNodes;
// Keep the instance of the running tracert as a field, we need it.
private Tracert pTracert;
public bool IsTracertRunning
{
get { return this.pTracert != null; }
}
public ObservableCollection<TracertNode> DoTrace(string hostOrIP, int maxHops, int timeOut)
{
// If we are not already running a tracert...
if (this.pTracert == null)
{
// Clear or creates the list of tracert nodes.
if (this.pTracertNodes == null)
this.pTracertNodes = new ObservableCollection<TracertNode>();
else
this.pTracertNodes.Clear();
var tracert = new Tracert();
tracert.HostNameOrAddress = hostOrIP;
tracert.MaxHops = maxHops;
tracert.TimeOut = timeOut;
tracert.NewNodeFound += delegate(Tracert sender, TracertNode newNode)
{
// This method is called inside Tracert thread.
// We need to use synchronization context to execute this method in our main window thread.
SynchronizationContext.Current.Post(delegate(object state)
{
// This method is called inside window thread.
this.OnTracertNodeFound(this.pTracertNodes, newNode);
}, null);
};
tracert.TracertCompleted += delegate(object sender, EventArgs e)
{
// This method is called inside Tracert thread.
// We need to use synchronization context to execute this method in our main window thread.
SynchronizationContext.Current.Post(delegate(object state)
{
// This method is called inside window thread.
this.OnTracertCompleted();
}, null);
};
tracert.Trace();
this.pTracert = tracert;
}
return this.pTracertNodes;
}
protected virtual void OnTracertCompleted()
{
// Remove tracert object,
// we need this to let the garbage collector being able to release that objects.
// We need also to allow another traceroute since the previous one completed.
this.pTracert = null;
System.Windows.MessageBox.Show("TraceRoute completed!");
}
protected virtual void OnTracertNodeFound(ObservableCollection<TracertNode> collection, TracertNode newNode)
{
// Add our tracert node.
collection.Add(newNode);
}
The issue is that not only is the listbox not updating, but my entire application hangs.
This is probably due to the AutoResetEvent blocking in DoTrace. You explicitly call Wait.WaitOne(); on the event handle, but as far as I can tell, never Set() it. This will cause the application to hang forever as soon as you call Wait.WaitOne().
It sounds like tracert.Trace() is an asynchronous method. Does it include some form of callback/event to notify you upon completion? If so, you should use that, not poll in a loop, to determine when it's complete.
(1) the listbox bound to tracertDataView is not updating
You won't see the updates to your listbox, as you're assigning a new collection to the TracertResultNodes property, the binding in this case simply does not work, because a new collection was assigned.
In addition to ensuring that the collection is updated in the same thread as outlined by Salvatore below, you should only add or remove items from the existing collection, and NOT assign the new one generated by your DoTrace function.
private void DoTrace_Click(object sender, RoutedEventArgs e)
{
foreach(var traceNode in _tracertWrapper.Results)
{
((App)Application.Current).TracertResultNodes.Add(traceNode);
}
_tracertWrapper.DoTrace("8.8.8.8", 30, 50);
}
If you do assign a new one, then you'd need to implement INotifyPropertyChanged on your App class, am not sure how (or whether) that would work though (I have not tried this before).
In a similar question:
What is this pattern called? Soft Lock?
I was asking about the name of the pattern for the code listing below.
public class MyClass
{
public event EventHandler MyEvent;
private bool IsHandlingEvent = false;
public MyClass()
{
MyEvent += new EventHandler(MyClass_MyEvent);
}
void MyClass_MyEvent(object sender, EventArgs e)
{
if (IsHandlingEvent) { return; }
IsHandlingEvent = true;
{
// Code goes here that handles the event, possibly invoking 'MyEvent' again.
// IsHandlingEvent flag is used to avoid redundant processing. What is this
// technique, or pattern called.
// ...
}
IsHandlingEvent = false;
}
}
It seems that most of the conversation was centered around why we should an should not do this, so I think that this question provides a better forum to tackle the problem and address all of the issues. What is the better / proper way to handle this?
There are series of problems with that pattern. If you want to invoke the handler only once, you would do something like this:
protected static object _lockObj = new object();
protected static bool _isHandled = false;
void MyClass_MyEvent(object sender, EventArgs e)
{
if(_isHandled)
return;
lock(_lockObj)
{
if(_isHandled)
return;
_isHandled = true;
MyOtherPossiblyRecursiveMethod(); // Actually does all your work
_isHandled = false;
}
}
void MyOtherPossiblyRecursiveMethod()
{
}
This way, only one thread should be able to access the actual work method.
I will use something like:
using( var sl = new SoftLock() )
{
sl.Execute(()=>{....});
}
the execute will raise the internal boolean to prevent re-entering. In the dispose that flag would be resetted. Execute will call the lambda just if the flag is false. This is to ensure flag go to false even if exception happens ( causing handler never executed ) and maybe is a little better to see. Of course this is not thread safe, as the original code, but this because we are talking about preventing double execution from the same thread.
The original code is a sufficient (and very lightweight) way to prevent recursion in a single-threaded app. So if during your event handling function you could get into code that might be firing the event again you will not enter infinite recursion.
But the code is not sufficient to prevent access from multiple threads, due to the potential for race conditions. If you need to ensure only one thread gets to run this event, then you should use a stronger locking mechanism, like a Mutex or Semaphore.
The following works in single- and multi-threaded scenarios and is exception-safe... also if need be it can be modified to allow for a certain level of reentrancy (for example 3 levels)...
public class MyClass
{
public event EventHandler MyEvent;
private int IsHandlingEvent = 0;
public MyClass()
{
MyEvent += new EventHandler(MyClass_MyEvent);
}
void MyClass_MyEvent(object sender, EventArgs e)
{
// this allows for nesting if needed by comparing for example < 3 or similar
if (Interlocked.Increment (ref IsHandlingEvent) == 1 )
{
try {
}
finally {};
}
Interlocked.Decrement (ref IsHandlingEvent);
}
}