Let us create a sample application. It has two kind of windows. One window would serve as Server Window. It has a responsibility of generating notifications for different parts in the same window or across different windows in the same or different processes.
It has three buttons and a textual notification. Signal button would give notification to the interesting thread created by the same window. Open another window opens a new window for consumption of signals. Signal another window generates notifications for the other window. The XAML for the above window is as follows:
<Window x:Class="WpfApplicationA_Signaling.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<Button Content="Signal" Height="35" HorizontalAlignment="Left"
Margin="23,129,0,0" Name="btnSignalSet" VerticalAlignment="Top" Width="147" Click="btnSignalSet_Click" />
<Label Content="Waiting for signal..." Height="38" HorizontalAlignment="Left" Margin="220,125,0,0"
Name="LabelStatus" VerticalAlignment="Top" Width="196" />
<Button Content="Open another window" Height="39" HorizontalAlignment="Left" Margin="23,184,0,0"
Name="button1" VerticalAlignment="Top" Width="147" Click="button1_Click" />
<Button Content="Signal another window" Height="39" HorizontalAlignment="Left" Margin="227,184,0,0"
Name="button2" VerticalAlignment="Top" Width="189" Click="button2_Click" />
</Grid>
</Window>
The code behind for this is as follows:
public partial class MainWindow : Window
{
static EventWaitHandle _waitHandle = new AutoResetEvent(false);
EventWaitHandle _waitHandleNamed = new EventWaitHandle(false, EventResetMode.ManualReset, "MyWaitHandle");
public MainWindow()
{
InitializeComponent();
new Thread(() =>
{
_waitHandle.WaitOne();
this.Dispatcher.BeginInvoke(new Action(() => this.LabelStatus.Content = "Signal received1"));
}
).Start();
new Thread(() =>
{
_waitHandle.WaitOne();
this.Dispatcher.BeginInvoke(new Action(() => this.LabelStatus.Content = "Signal received2"));
}
).Start();
}
private void btnSignalSet_Click(object sender, RoutedEventArgs e)
{
_waitHandle.Set();
}
private void button1_Click(object sender, RoutedEventArgs e)
{
new OtherWindow().Show();
}
private void button2_Click(object sender, RoutedEventArgs e)
{
_waitHandleNamed.Set();
}
}
In the above code, we are creating an AutoResetEvent, named _waitHandle. In the constructor, we are waiting for this EventWaitHandle in two different threads by calling WaitOne on this EventWaitHandle. As soon as the thread is notified, we are setting the content of LabelStatus as Signal received1 for first thread and Signal received2 for second thread. When the constructor ends, two threads are ready to receive notification. As soon as they would receive the signal, they can resume their operation.
new Thread(() =>
{
_waitHandle.WaitOne();
this.Dispatcher.BeginInvoke(new Action(() => this.LabelStatus.Content = "Signal received1"));
}
).Start();
new Thread(() =>
{
_waitHandle.WaitOne();
this.Dispatcher.BeginInvoke(new Action(() => this.LabelStatus.Content = "Signal received2"));
}
).Start();
Since this is an AutoResetEvent, each thread would wait for its turn. A signal is received by only one. This one is generally the one at the top of the queue.
Now run the application. Click on Signal button. The first thread in the queue gets the signal. This thread now can resume its operation and updates the Label Status as Signal received1.
Now hit the Signal button again. Now second thread gets the notification and updates the LabelStatus as Signal received2.
You might have noticed (in the event handler for Click event for button1, button1_Click), we are showing OtherWindow. The OtherWindow is defined as follows:
<Window x:Class="WpfApplicationA_Signaling.OtherWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="OtherWindow" Height="300" Width="300">
<Grid>
<TextBlock Height="31" HorizontalAlignment="Left" Margin="8,63,0,0"
Name="textBlock1" Text="TextBlock" VerticalAlignment="Top" Width="261" />
</Grid>
</Window>
The code behind for this window is as follows:
public partial class OtherWindow : Window
{
EventWaitHandle _waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, "MyWaitHandle");
public OtherWindow()
{
InitializeComponent();
new Thread(() =>
{
_waitHandle.WaitOne();
this.Dispatcher.BeginInvoke(new Action(() => this.textBlock1.Text = "Signal received"));
}
).Start();
}
}
In this window _waitHandle is the similar EventWaitHandle as in MainWindow. This is a ManualReset type of EventWaitHandle. The name of event is “MyWaitHandle”. Like MainWindow’s, here we are starting a new thread which is waiting for this EventWaitHandle. It must be remembered that as soon as a Signal is received for ManualReset EventWaitHandle, all threads waiting for the signal gets this notification and they resume their operation.
After signal has been generated, any thread which is waiting or will be waiting in future finds this open gate and just continue running unless the EventWaitHandle resets. In order to test this, lets click Open another window button three times showing three instances of AnotherWindow.
As we click the button “Signal another window”, the following code executes:
private void button2_Click(object sender, RoutedEventArgs e)
{
_waitHandleNamed.Set();
}
which is nothing but signaling the waiting threads on this event to resume operation (ManualReset). The threads in all the Window updates their respective Label contents by following code:
this.Dispatcher.BeginInvoke(new Action(() => this.textBlock1.Text = "Signal received"));
The windows are updates as follows:
Basically, the following statement creates a System level EventWaitHandle:
EventWaitHandle _waitHandle = new EventWaitHandle(false, EventResetMode.ManualReset, "MyWaitHandle");
We have to make sure that the name of this event is unique so that other applications and processes are not affected by these signals. Here we have defined a ManualRestEvent, we could also define an AutoReset EventWaitHandle. Then clicking button each time a different window would receive the signal and update the content of its Label. In order to prove the point of System level EventWaitHandle, let us create a new WPF project, WpfApplicationB_Signaling. This has just one window, MainWindow. The Window is defined as follows:
<Window x:Class="WpfApplicationB_Signaling.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="MainWindow" Height="350" Width="525">
<Grid>
<TextBlock Height="39" HorizontalAlignment="Left" Margin="46,43,0,0"
Name="txtNotificationStatus" Text="Waiting For Signal!"
VerticalAlignment="Top" Width="368" />
</Grid>
</Window>
The code behind of the Window is as follows:
public partial class MainWindow : Window
{
EventWaitHandle _waitHandleNamed = new EventWaitHandle(false, EventResetMode.AutoReset,
"MyWaitHandle");
public MainWindow()
{
InitializeComponent();
ThreadPool.RegisterWaitForSingleObject(_waitHandleNamed, (o, e) =>
{
this.Dispatcher.BeginInvoke(new Action(() =>
this.txtNotificationStatus.Text = "Signal received"));
},
null, Timeout.Infinite, true);
}
}
Here we are using ThreadPool functionality of handling EventWaitHandle. We are registering EventWaitHandle using RegisterWaitForSingleObject method of ThreadPool. For this method, we need to specify which Event to wait on (_waitHandleNamed). We also specify if there is a Timeout. Since we can wait forever, we are specifying Timeout as Timeout.Infinite. The last argument is specified as true. This is to specify that we are interested in just one signal, otherwise, after finishing executing the handler, it would continue to wait for more notifications.
Now run this along with the previous WPF project. As you can see that we have opened three instances of AnotherWindow from 1st process. We also have MainWindow of second process opened:
Now click “Signal another window” button. All three AnotherWindow(s) and MainWindow from 2nd process updates as follows:
It is just like water flow. All taps which are opened, should get water from the flow:
Resume, only when all operations are completed!
In the above examples, we have considered the examples of AutoReset and ManualRest EventWaitHandles. .Net 4.0 has introduce a new signaling mechanism between threads. This is called CountdownEvent. Though this is named like the other two and it is also used in signaling between threads, it does not inherit EventWaitHandle unlike other two.
http://msdn.microsoft.com/en-us/library/system.threading.countdownevent.aspx
In this example, we can only notify the user when three operations complete. In order to keep the example simple, we are using three buttons. The click of each button would be considered as completion of an operation. When all three operation completes, the textblock should notify the user with text Process Finshed!!!. Just click each button only once :)
The definition of the above window is as follows:
<Window x:Class="WpfApplicationB_Signaling.WindowCountdownEventExample"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
Title="WindowCountdownEventExample" Height="357" Width="669">
<Grid>
<Button Content="Execute Operation 1" Height="35" HorizontalAlignment="Left"
Margin="34,70,0,0" Name="btnOperation1" VerticalAlignment="Top" Width="185" Click="btnOperation1_Click" />
<Button Content="Execute Operation 2" Height="36" HorizontalAlignment="Left"
Margin="34,130,0,0" Name="btnOperation2" VerticalAlignment="Top" Width="185" Click="btnOperation2_Click" />
<Button Content="Execute Operation 3" Height="32" HorizontalAlignment="Left"
Margin="33,190,0,0" Name="btnOperation3" VerticalAlignment="Top" Width="188" Click="btnOperation3_Click" />
<TextBlock Height="33" HorizontalAlignment="Left" Margin="287,124,0,0"
Name="textNotificationProcessFinish" Text="Waiting for process completion..."
VerticalAlignment="Top" Width="283" />
</Grid>
</Window>
The code behind is as follows:
public partial class WindowCountdownEventExample : Window
{
CountdownEvent _waitCountDown = new CountdownEvent(3);
public WindowCountdownEventExample()
{
InitializeComponent();
new Thread(() =>
{
_waitCountDown.Wait();
this.Dispatcher.BeginInvoke(new Action(() => this.textNotificationProcessFinish.Text = "Process Finshed!!!"));
}
).Start();
}
private void btnOperation1_Click(object sender, RoutedEventArgs e)
{
_waitCountDown.Signal();
}
private void btnOperation2_Click(object sender, RoutedEventArgs e)
{
_waitCountDown.Signal();
}
private void btnOperation3_Click(object sender, RoutedEventArgs e)
{
_waitCountDown.Signal();
}
}
The above code is very simple. We have created an instance of CountdownEvent with the required signal count as 3. In the constructor, we have created a thread waiting for the CountdownEvent to finish. After each button click we are generating a signal. When the signal count reaches 3, the thread can resume. Now run the application and click each button only once. As soon as we click the third button, the window is updates as follows:
Zindabad!!!
Note: Since CountdownEvent does not inherit EventWaitHandle, there is no named CountdownEvent available in .net 4.0. Had there been any named event available for this type, we might implement many scenarios efficiently just by using CountdownEvent. This seems to be a big limitation.
No comments:
Post a Comment