I was trying to do async loop on keydown, to move image as long as button is pressed.
private async void Window_KeyDown(object sender, KeyEventArgs e)
{
await Task.Run(() =>
{
while (e.IsDown)
{
if (e.Key.ToString() == "D")
Width.Text = (int.Parse(Width.Text) - 10).ToString();
}
});
}
But it cousing an error: InvalidOperationException, and mscorlib.pdb not loaded.
Add a bool to your form ( bool isDDown = false )
on the keydown event set isDDown = true;
on the keyup event set isDDown = false;
add a timer to your form and check however often you need to, update if true.
It wont be quite as continuous as this one is, but it should get rid of your error
Pretty simple way, and perhaps overly complex way to do it. Spawn a thread with a while loop which continuously adds to your GUI thread's dispatch the task you want to do continuously. It does this aslong you are holding down the mouse button, when you let go, it stops adding jobs to do, and any jobs queued will fail because of a check before it actually does any work
bool isGoing = false;
private void MouseMouseDown(object sender, MouseButtonEventArgs e)
{
isGoing = true;
new Thread(new ThreadStart(() => { while (isGoing) { Dispatcher.BeginInvoke(DispatcherPriority.Input, new ThreadStart(() => { if (isGoing)
{
//Whatever you want to happen continuously ie. move your image
}}));}})).Start();
}
private void MouseUp(object sender, MouseButtonEventArgs e)
{
isGoing = false;
}
This will get rid of your error, and be continuous.
Note:
I did this with mouse presses, but it will work exactly the same with keypresses
The speed of your image moving will depend on how good the computer is, I recommend either, using a timer, or even better, use delta time calculations when moving the image. that way you can move the image X/per second, but keep the position updating every possible chance, which results in the smoothest and most consistent motion.
Related
I hand-rolled a MVC-style implementation of a game that I want to autoplay. By "autoplay" I mean that the buttons that normally a user would click while playing I want a controller to automatically initiate. That way I can watch the game play itself for quality control reasons. This particular game has a lot of code, so instead of providing it as an example I've created a silly HelloWorld example using the same approach.
Before I provide the example, here is my issue: everything you see below is functional, and "works"; except for one thing: I'm unable to shut-off the autoplay because the UI becomes unresponsive and the button to turn it off won't respond to a click event.
First create a .Net 4.6.1 winforms project in a solution. (.net version probably doesn't matter as long as it is >= 4.5). Create a Form that looks like this:
In the code behind, copy paste this: (change names as needed to compile)
using System;
using System.Threading;
using System.Windows.Forms;
namespace WinformsExample
{
public partial class HelloWorldView : Form
{
private readonly HelloWorldController MyHelloWorldController;
public HelloWorldView()
{
InitializeComponent();
MyHelloWorldController = new HelloWorldController();
}
private void button1_Click(object sender, EventArgs e)
{
MyHelloWorldController.HelloWorldRequested();
if (MyHelloWorldController.IsAutomated)
{
Thread.Sleep(2000);
button1.PerformClick();
}
}
private void HelloWorldView_Load(object sender, EventArgs e)
{
MyHelloWorldController.HelloWorldRequestedEvent += OnHelloWorldRequested;
}
private void OnHelloWorldRequested(HelloWorldParameters parameters)
{
textBox1.Text += parameters.HelloWorldString + Environment.NewLine;
textBox1.Update();
}
private void button2_Click(object sender, EventArgs e)
{
MyHelloWorldController.IsAutomated = !MyHelloWorldController.IsAutomated;
if (MyHelloWorldController.IsAutomated)
{
button2.Text = "hello world - is on";
button2.Update();
button1.PerformClick();
}
else
{
button2.Text = "hello world - is off";
button2.Update();
}
}
}
}
And create a class titled HelloWorldController.cs and copy paste this in to it:
namespace WinformsExample
{
public class HelloWorldParameters
{
public string HelloWorldString { get; set; }
}
public delegate void HelloWorldEventHandler(HelloWorldParameters parameters);
public class HelloWorldController
{
private readonly HelloWorldParameters _parameters;
public event HelloWorldEventHandler HelloWorldRequestedEvent;
public bool IsAutomated { get; set; }
public HelloWorldController()
{
_parameters = new HelloWorldParameters();
}
public void HelloWorldRequested()
{
_parameters.HelloWorldString = "Hello world!!";
if (HelloWorldRequestedEvent != null)
HelloWorldRequestedEvent(_parameters);
}
}
}
...go ahead and rename things if you need to. Now build the program. Click the first button. You will see "hello world". Now click the second button, you will see "hello world" printed every 2 seconds.
The way I thought this would work is that by clicking button2 a second time, that it would stop the autoplay. However, the UI is unresponsive and the button click event never happens.
What is going on here that is causing the UI to be unresponsive and how can I fix it so that I get the intended behavior?
*UPDATE - HERE IS THE SOLUTION *
Keep everything the same as above except for HelloWorldView.cs. Remove the call to Thread.Sleep(). Drag and drop a timer from the toolbox to the design surface. You will see an icon on the bottom of the designer surface labeled
timer1
Copy paste the following code in to HelloWorldView.cs. Compile and execute. If everything is correct you should be able to turn on and off the "hello world" display by clicking the button at any time - the UI stays responsive.
using System;
using System.Windows.Forms;
namespace WinformsExample
{
public partial class HelloWorldView : Form
{
private readonly HelloWorldController MyHelloWorldController;
public HelloWorldView()
{
InitializeComponent();
MyHelloWorldController = new HelloWorldController();
}
private void onTimerTick(object sender, EventArgs e)
{
button1.PerformClick();
}
private void OnHelloWorldRequested(HelloWorldParameters parameters)
{
textBox1.Text += parameters.HelloWorldString + Environment.NewLine;
textBox1.Update();
}
private void HelloWorldView_Load(object sender, EventArgs e)
{
MyHelloWorldController.HelloWorldRequestedEvent += OnHelloWorldRequested;
}
private void button1_Click(object sender, EventArgs e)
{
MyHelloWorldController.HelloWorldRequested();
}
private void button2_Click(object sender, EventArgs e)
{
MyHelloWorldController.IsAutomated = !MyHelloWorldController.IsAutomated;
if (MyHelloWorldController.IsAutomated)
{
button2.Text = "hello world - is on";
button2.Update();
timer1.Interval = 2000;
timer1.Tick += onTimerTick;
timer1.Start();
}
else
{
timer1.Stop();
button2.Text = "hello world - is off";
button2.Update();
}
}
}
}
WinForms uses a single message pump thread (called the UI thread). (If you are unfamiliar with the concept you should research Windows messages and Windows message pump).
Thread.Sleep causes the currently executing thread the sleep, or pause, for a time. This sleep/pause is like death to the thread - it is aware of nothing and unable to do anything.
As the currently executing thread in a WinForms app is usually the UI thread - Thread.Sleep will cause the UI to become unresponsive because it is no longer able to pump messages.
An alternative design would be to use a form-based Timer. Place your game playing code in the Timer's Tick event.
What is going on here that is causing the UI to be unresponsive and how can I fix it so that I get the intended behavior?
There are essentially two reasons why your app becomes unresponsive.
1. Thread.Sleep() in UI thread
GUI applications on Windows are generally driven by messages (mouse clicks; keyboard; screen drawing) posted to it which are placed on a queue. The UI thread processes these messages one by one dispatching the message to the appropriate handler. In this way it is known as the Message Pump. If during processing one of these messages too much time elapses, then the UI will appear to freeze. Event handlers should be as fast as possible.
During your click handlers you are using Thread.Sleep(2000); which will prevent the UI thread from updating the UI of your application, in essence simulating an event handler that takes far too long to process an event. It is perhaps no different to say performing a lengthy database or WCF operation on the UI thread, hence why people tend to put such calls on a separate thread or task.
Recommend you remove the Thread.Sleep and replace it with a timer as others have indicated.
2. Infinite Recursive Loop on button1 handler
When you click button2 for the first time, the click handler for button2 is invoked where automation is enabled. You then simulate button1 being clicked via button1.PerformClick();.
During the call to button1.PerformClick, the click handler for button1 button1_Click() is invoked. It is there that you sleep for 2 seconds (which isn't healthy for the UI) but the secondary problem is that you immediately call button1.PerformClick(); from inside the button1 click handler, in essence setting up an infinite recursive loop.
If you were to remove the Thread.Sleep(2000) your app will eventually lead to a StackOverflowException. Your code as it stands now (even with the sleep) will still overflow, it's just that it will take much longer to become apparent.
Again, consider replacing it with a timer.
3. Exclusivity
It's important to note that ignoring the stack fault for a moment, the design is such that your app can't do anything else whilst this infinite loop is running. So if your game had other buttons to click; scores to display; sound effects to play; all from the point of view of the button2 handler, most likely it will never happen because it is too busy exclusively processing button1.
Conclusion
Keep UI responsive: Avoid Thread.Sleep() in your code
Avoid recursion: Don't use PerformClick() for a button whilst you are inside the click handler for said button
Your "Thread.Sleep()" call puts the UI thread to sleep. Use a Timer instead. Then terminate the Timer on the second press. (You could also do this with Tasks, if you want to use another thread you need to make the 2 threads communicate in someway so that the UI thread is the only one actually updating the UI)
Desktop applications have a so called UI thread. It's basically an infinite loop which keeps checking if something happened, such as a mouse click, and redraws the window if needed. Coding in WinAPI you would need to write this loop yourself, WinForms and other UI frameworks hide it away. But your click handler is called from inside this loop. So if your code takes too much time - like, because you call Thread.Sleep inside - the loop will not continue and will not be able to process anything that is happening to the application. This why long-running processes need to take place on a separate thread.
As others have said, you are blocking the UI thread with the Thread.Sleep and recursive button1.PerformClick(); call. You have to let the UI run as freely as possible and let it go idle quickly.
So, just for the fun of it I have rewritten your code to do just that. I've also implemented it with Microsoft's Reactive Extensions (Rx) - just NuGet "Rx-WinForms" to get the bits. Rx allows you to do some very funky things that you can't easily do with events.
Here's your form now:
public partial class HelloWorldView : Form
{
private readonly HelloWorldController MyHelloWorldController =
new HelloWorldController("Hello world!!", TimeSpan.FromSeconds(1.0));
public HelloWorldView()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
MyHelloWorldController.Messages
.ObserveOn(this)
.Subscribe(message =>
{
textBox1.Text += message + Environment.NewLine;
});
MyHelloWorldController.IsAutomateds
.ObserveOn(this)
.Subscribe(isAutomated =>
{
button2.Text = "hello world - is " + (isAutomated ? "on" : "off");
});
}
private void button1_Click(object sender, EventArgs e)
{
MyHelloWorldController.Trigger();
}
private void button2_Click(object sender, EventArgs e)
{
MyHelloWorldController.IsAutomated = !MyHelloWorldController.IsAutomated;
}
}
You'll notice that I've simplified down the UI. It really does as little as possible to update itself and to notify the HelloWorldController of its actions.
The worst part of the code are the two .Subscribe calls in Form1_Load. These are simply looking at the two observables (Rx's version of events if you like) and makes sure the events are run on the UI thread with the .ObserveOn(this) call, and then they subscribe to values produced from the HelloWorldController.
The UI is simply updating itself from the controller and telling the controller what it is doing. There is virtually no logic being performed in the UI. This is how it should be with any MVC-style coding.
Now the HelloWorldController is where the fun is.
It starts off pretty simply:
private string _message;
private TimeSpan _automatedPeriod;
public HelloWorldController(string Message, TimeSpan automatedPeriod)
{
_message = Message;
_automatedPeriod = automatedPeriod;
}
This is basically the information about what message to send to the UI and how often when the controller is automating the values.
It then tracks whether it is automated or not:
private bool _isAutomated = false;
Now it contains the Rx observables - these are like the events you were using.
private Subject<string> _messages = new Subject<string>();
public IObservable<string> Messages { get { return _messages.AsObservable(); } }
private Subject<bool> _isAutomateds = new Subject<bool>();
public IObservable<bool> IsAutomateds { get { return _isAutomateds.AsObservable(); } }
private SerialDisposable _serialSubscription = new SerialDisposable();
In Rx an IObservable<T> is something I can subscribe to to get a series of values - just like an event. The Subject<T> is something that I can manually push values into, but it also can be an IObservable<T> that can be subscribed to. It's the pair of these that lets me raise events. Think of the Subject<string> to be the equivalent of the HelloWorldRequested method in your code and the IObservable<string> to be the equivalent of the HelloWorldRequestedEvent event.
If I call _messages.OnNext("Hello") then any subscribers to IObservable<string> Messages would get a "Hello" sent to them. Just like an event.
IsAutomated looks like this:
public bool IsAutomated
{
get { return _isAutomated; }
set
{
_isAutomated = value;
_isAutomateds.OnNext(value);
if (_isAutomated)
{
this.Trigger();
}
}
}
So it does its job of updating its own internal state, but it also calls _isAutomateds.OnNext(value) to push out the updates to any subscribers of IObservable<bool> IsAutomateds. It also works out if it needs to trigger the controller to produce messages with the this.Trigger() call.
Finally the Trigger method looks like this:
public void Trigger()
{
if (_isAutomated)
{
_serialSubscription.Disposable =
Observable
.Interval(_automatedPeriod)
.StartWith(0)
.TakeUntil(_isAutomateds.Where(x => x == false))
.Subscribe(n => _messages.OnNext(_message));
}
else
{
_messages.OnNext(_message);
}
}
The easy part of this is when the _isAutomated is false then it simply sends one message out via the _messages.OnNext(_message) call.
When _isAutomated is true it uses some of the coolness of Rx to set up effectively a timer to produce values every TimeSpan _automatedPeriod. From your code you wanted every 2 seconds so the TimeSpan would be TimeSpan.FromSeconds(2.0).
Observable.Interval(_automatedPeriod) defines a timer that begins producing values after the first period of time and then every period of time between.
So the .StartWith(0) says that it should immediately produce a value when it is subscribed to.
The .TakeUntil(_isAutomateds.Where(x => x == false)) is the best part here - it says that it will take the values from the the Observable.Interval(_automatedPeriod).StartWith(0) and stop when it gets a value from _isAutomateds.Where(x => x == false) - in other words when the IsAutomated is set to false.
The .Subscribe(n => _messages.OnNext(_message)); simply pushes a value to the _messages subject so that all subscribers of IObservable<string> Messages gets their messages.
Just put all of the HelloWorldController I've given you in public class HelloWorldController { ... } and you're good to go.
The works I think like it should and shows how lightweight the UI code can be.
I hope you find this worth playing with.
You'll need to add these using's to the top of your code to get all of the code to compile:
using System.Reactive.Disposables;
using System.Reactive.Linq;
using System.Reactive.Subjects;
I'm using the Keithley 2100 digital multimeter to gather VAC readings for a piece of calibration software i'm writting. I've made a small test program to gather some data on Keithley's IVI Class Library that can be downloaded from their website.
I'm running a background worker which is gathering outputs from the multimeter, see code;
private void readButton_Click(object sender, EventArgs e) // gather readings
{
if (!backgroundWorker1.IsBusy)
{
address = Ke2100FunctionEnum.Ke2100FunctionACVolts;
range = Double.Parse(textBox2.Text);
resolution = Double.Parse(textBox3.Text);
backgroundWorker1.RunWorkerAsync();
}
else
{
MessageBox.Show("Task already enabled");
}
}
This is my gather reading button, it checks to make sure the background worker isn't busy, then runs the worker.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
Invoke(new Action(() =>
{
ACResult = ke2100Device.Measure(address, range, resolution);
richTextBox1.Text += ACResult.ToString() + "\n";
}));
if(backgroundWorker1.CancellationPending)
{
backgroundWorker1.Dispose();
e.Cancel = true;
return;
}
Thread.Sleep(10);
}
}
It takes around a second for the ke2100Device.Measure function to process one reading, but in this time period the whole program becomes unresponsive, which I just can't have in my program. I've loaded up the task manager to see if any of my cores are on 100%, as it seems like quite an intensive function, but my usage is just fine.
I'm a little stumped on how to get fix this issue. I've commented out the ke2100Device.Measure function and just had the rich text box add random numbers, this works as expected with no unresponsiveness.
The only ideas I have just seem to be another way of doing the same thing... Coffee break!
-- Edit --
Updated code;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
ACResult = ke2100Device.Measure(address, range, resolution);
Invoke(new Action(() => { richTextBox1.Text += ACResult.ToString() + "\n"; }));
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
return;
}
Thread.Sleep(10);
}
}
Though if I run this debug code to check my bgw;
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
//ACResult = ke2100Device.Measure(address, range, resolution);
Invoke(new Action(() => { richTextBox1.Text += 0 + "\n"; })); //ACResult.ToString()
if (backgroundWorker1.CancellationPending)
{
e.Cancel = true;
return;
}
Thread.Sleep(10);
}
}
Then I don't get any hangs, perhaps there is an actual issue with the Measure function? Could it be doing something I'm not fully realising or seeing?
The call to Measure should be outside of the Invoke action. Calling it inside the Invoke effectively runs it on the UI thread, rendering your background worker meaningless.
private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
{
while (true)
{
ACResult = ke2100Device.Measure(address, range, resolution);
Invoke(new Action(() => { richTextBox1.Text += ACResult.ToString() + "\n"; }));
if(backgroundWorker1.CancellationPending)
{
//backgroundWorker1.Dispose(); // I don't think you want this here!
e.Cancel = true;
return;
}
Thread.Sleep(10);
}
}
http://msdn.microsoft.com/en-us/library/cc221403(v=vs.95).aspx
As you want to notify of the state on each measure you probably want to use the the ProgressChanged() event that the BackgroundWorker exposes. You can set the UserState property when you call ReportProgress().
Doing this will mean you don't have to think about whether to call Invoke() or not as the BackgroundWorker will hide this implementation detail for you.
Also, if you update your GUI every 10ms i.e. 100 times per second you user probably won't be able to notice the different updates. You might want to change this value to be configurable and then play with it to get the desired refresh rate.
I have found the issue. I went straight into the basics of how the device communicates with my laptop, and found out that it uses SCPI commands, so from this point I started making two really simple functions that creates a connection, and then sends a command to the multimeter.
After this point I realised that all commands being sent to the multimeter and back are done on the command line, which then lead me to believe that the command line and GUI thread are actually the same thread, which would explain why the whole program would hang when trying to read data from my device.
How did I fix this? Easily, I put my app on another thread before loading it up, see code!
Thread applicationThread = new Thread(() => Application.Run(new Form1()));
applicationThread.Start();
No more hanging! I hope this can help other people down the line. Thanks for the help guys and girls!
I am making an app for wp 7.x/8.
There is a function like -
public void draw()
{
.............
ImageBrush imgbrush = new ImageBrush();
imgbrush.ImageSource = new BitmapImage(...);
rect.Fill = imgbrush; //rect is of type Rectangle that has been created
//and added to the canvas
........
}
and another function
private void clicked(object sender, RoutedEventArgs e)
{
draw();
........
if (flag)
{
Thread.sleep(5000);
draw();
}
}
But when the button is clicked the result of both the draw operation appear simultaneously on the screen.
How to make the result of second draw() operation to appear after some delay?
Or is there something like buffer for the screen, until the buffer is not filled the screen will not refresh?
In that case, how to FLUSH or Refresh the screen explicitly or force the .fill() method of Rectangle to make the changes on the screen?
Any help in this regard would be highly appreciated.
As pantaloons says, since all of your actions are on the same thread (the first draw, the sleep, and the second draw), the UI itself never gets a chance to update. However, there is a slightly better implementation (though it follows the same principal as the aforementioned suggestion).
By using a timer, you can let it kick the wait to another thread, allowing the UI to update from the first draw before doing the second, like so:
private void clicked(object sender, RoutedEventArgs e)
{
draw();
........
if (flag)
{
var timer = new DispatcherTimer { Interval = TimeSpan.FromSeconds(5) };
timer.Tick += (sender, args) => { timer.Stop(); draw(); };
timer.Start();
}
}
In this solution, all the invocation is handled by the DispatcherTimer (which will automatically call back to the UI thread). Also, if draw needs to be called more than twice in a row, the timer will continue to tick until stopped, so it would be very straightforward to extend to include a count.
The problem is that you are blocking the UI thread by sleeping, so the draw messages are never pumped until that function returns, where the changes happen synchronously. A hacky solution would be something like the following, although really you should change your app design to use the async/await patterns.
private void clicked(object sender, RoutedEventArgs e)
{
draw();
if (flag)
{
System.Threading.Tasks.Task.Run(() =>
{
System.Threading.Thread.Sleep(5000);
Deployment.Current.Dispatcher.BeginInvoke(() =>
{
draw();
});
});
}
}
So I have two event handlers button1_Click() and button2_Click()
In button1_Click() I have something running like this:
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
//DoStuff
}
But button2_Click is supposed to be a stop button, and stop button1 early.
How do I go about this?
Thanks for the help. I saw this article here about it, but couldn't get it to work.
Windows.Forms answer
The least sophisticated method is this:
private bool m_stop;
private void button1_Click (object s, EventArgs ea)
{
try
{
// Don't forget to disable all controls except the ones you want a user to be able to click while your method executes.
toGet = textbox1.Text;
got = 0;
while (got <= toGet)
{
Application.DoEvents ();
// DoEvents lets other events fire. When they are done, resume.
if (m_stop)
break;
//DoStuff
}
finally
{
// Enable the controls you disabled before.
}
}
private void button2_Click (object s, EventArgs ea)
{
m_stop = true;
}
It has the distinct advantage of letting you execute button1_Click on the UI thread, still lets the UI respond to your stop button.
It has a disadvantage that you must protect against reentrancy. What happens if they click your button1 while button1_click is already executing!?!?
Edit: Another way I have used is to use a Timer instead of a loop. Then, the stop method just stops the timer.
As much as I understood, correct me if I'm wrong, you're on single thread.
Wired, but you can check for single boolean value inside the your While loop, just as post suggested.
May be to make life easier (may be this is what "couldn't get it to work" means) is inside loop call
1) Windows Forms: Application.DoEvents()
2) WPF (little bit more tricky) : DoEvents in WPF
This to make breathe system.
You need to start the process inside the button1 in new thread, and when you press the button2 flag a local variable to false to stop the loop. like:
using System.Threading;
private volatile bool _requestStop = false;
private readonly object _oneExecuteLocker = new object();
private void OnButton1Click(ojbect sender, EventArgs e)
{
new Thread(() =>
{
if (Monitor.TryEnter(_oneExecuteLocker))
{//if we are here that is means the code is not already running..
try
{
while (!_requestStop)
{
//DoStuff
}
}
finally
{
Monitor.Exit(_oneExecuteLocker);
}
}
}){ IsBackground = true }.Start();
}
private void OnButton2Click(object sender, EventArgs e)
{
_requestStop = true;
}
Notes:
When ever you want to update a UI control inside the newly created thread you should use contorl.Invoke(/*the code get/set or call method in the UI*/).
The Monitro.Enter is just to be sure that your code will not executed multiple time per click if it already running.
I have a Grid control and clicking on each row does some background job to load the data. Each background job is performed on a thread pool thread. When user clicks on the items quickly, lot of requests to load data will be queued. I want to minimize this by providing a delay after clicking each row. There will be some delay before firing the request to load the data.
I am thinking about using DispatcherTimer class. Something like,
readonly DispatcherTimer dt = new DispatcherTimer();
private void Clicked(object sender, RoutedEventArgs e)
{
dt.Interval = TimeSpan.FromSeconds(2);
dt.Stop();
dt.Start();
}
private void DtOnTick(object sender, EventArgs args)
{
// Fire a thread and do data loading
}
Is this the correct way to approach the problem?
Any suggestions would be appreciated!
How about disabling the control until the job is finished? Or disabling once the queue of jobs to do reaches a certain size? This would be a simple solution to prevent users from "clicking too much". And this way the delay would scale with the efficiency of your solution/speed of the computer.
The way you're trying to do it would just delay the problem itself for 2 seconds. All the clicks would just be handled two seconds later.
You might try to use a worker thread. Lets say you use a queue which takes information about each item that was clicked at the time it was clicked. An existing thread, created when the class is created, is notified when new items are added to the queue. The thread takes the first item, processes it, updates the UI. If there are more items, it takes the next one, processes it, etc. When there are no more items, the thread goes to sleep until new items are available (ManualResetEvent will help here).
The pattern would be:
void ItemClicked(...)
{
lock (WorkQueue)
{
QueueNewClickItem(...);
m_workToDo.Set();
}
}
void WorkerThread(...)
{
bool threadShouldEnd = false;
while (!threadShouldEnd)
{
if (WaitHandle.WaitAny(m_workToDo, m_endThread) == 0)
{
lock (WorkQueue)
{
CopyAllPendingWorkItemsToListInThread();
ClearWorkQueue();
m_workToDo.Reset();
}
while (!AllLocalItemsProcessed)
{
ProcessNextWorkItem();
}
}
else
{
threadShouldEnd = true;
}
}
}
What you actually want to do is something like this:
private DateTime? _NextAllowedClick;
private void Clicked(object sender, RoutedEventArgs e)
{
if (_NextAllowedClick != null && DateTime.Now < _NextAllowedClick)
{
return;
}
_NextAllowedClick = DateTime.Now + new TimeSpan(0, 0, 0, 2);
...
}