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;
Related
I'm writing a simple game that uses timers from the system.threading namespace to simulate wait times for actions. My goal is to have the timer execute once every second for x amount of seconds. To achieve this I added a counter in the timer callback.
The problem is any code I place after invoking the DeliveryProgressChangedEvent event seems to get ignored. My counter is never incremented thus allowing the timer to run forever.
If I invoke the event after I increment the counter, everything works fine. Just nothing after invoking the event will execute. Rather than going the easy route I'd like to understand if not resolve this problem.
I did a fair bit of research into the system.threading timer object as well as events but wasn't able to find any information related to my issue.
I created a bare bones example of my project to demonstrate the issue below.
Game Class
class Game
{
private Timer _deliveryTimer;
private int _counter = 0;
public event EventHandler DeliveryProgressChangedEvent;
public event EventHandler DeliveryCompletedEvent;
public Game()
{
_deliveryTimer = new Timer(MakeDelivery);
}
public void StartDelivery()
{
_deliveryTimer.Change(0, 1000);
}
private void MakeDelivery(object state)
{
if (_counter == 5)
{
_deliveryTimer.Change(0, Timeout.Infinite);
DeliveryCompletedEvent?.Invoke(this, EventArgs.Empty);
}
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
++_counter;
}
}
Form Class
public partial class Form1 : Form
{
Game _game = new Game();
public Form1()
{
InitializeComponent();
_game.DeliveryProgressChangedEvent += onDeliveryProgressChanged;
_game.DeliveryCompletedEvent += onDeliveryCompleted;
pbDelivery.Maximum = 5;
}
private void onDeliveryProgressChanged(object sender, EventArgs e)
{
if (InvokeRequired)
pbDelivery.BeginInvoke((MethodInvoker)delegate { pbDelivery.Increment(1); });
MessageBox.Show("Delivery Inprogress");
}
private void onDeliveryCompleted(object sender, EventArgs e)
{
MessageBox.Show("Delivery Completed");
}
private void button1_Click(object sender, EventArgs e)
{
_game.StartDelivery();
}
}
EDIT
Just to clarify what I mean. Any code I put after DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty); will not execute. In my example ++_counter will not run. The event does fire and the onDeliveryProgressChanged handler does run.
The problem:
Using a System.Threading.Timer class, when the TimerCallback is called, events are raised, to notify the subscribers of the DeliveryProgressChangedEvent and DeliveryCompletedEvent of custom Game class of the progress of a procedure and the termination of it.
In the sample class, the subscriber (a Form class, here) updates an UI, settings the value of a ProgressBar control and also showing a MessageBox (used in the actual implementation of the class sample shown here).
It appears that after the first event is invoked:
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
++_counter;
the line where the _counter should be increased is never reached, thus the code that inspects the _counter to set the Timer to a new value is never executed.
What happens:
The System.Threading.Timer is served by ThreadPool threads (more than one). Its callback is called on a thread other than the UI thread. The events invoked from the callback are also raised in a ThreadPool thread.
The code in the handler delegate, onDeliveryProgressChanged, is then run on the same Thread.
private void onDeliveryProgressChanged(object sender, EventArgs e)
{
if (InvokeRequired)
pbDelivery.BeginInvoke((MethodInvoker)delegate { pbDelivery.Increment(1); });
MessageBox.Show("Delivery Inprogress");
}
When the MessageBox is shown - it's a Modal Window - it blocks the Thread from where it's run, as usual. The code following the line where the event is invoked is never reached, so _counter is never increased:
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
++_counter;
The System.Threading.Timer can be served by more than one thread. I'm just quoting the Docs on this point, it's quite straightforward:
The callback method executed by the timer should be reentrant, because
it is called on ThreadPool threads. The callback can be executed
simultaneously on two thread pool threads if the timer interval is
less than the time required to execute the callback, or if all thread
pool threads are in use and the callback is queued multiple times.
What happens, in practice, is that while the Thread where the CallBack is executed, is blocked by the MessageBox, this doesn't stop the Timer from executing the CallBack from another thread: a new MessageBox is shown when the event is invoked and it keeps on running until it has resources.
The MessageBox has no Owner. When a MessageBox is shown without specifying the Owner, its class uses GetActiveWindow() to find an Owner for the MessageBox Window. This function tries to return the handle of the active window attached to the calling thread's message queue. But the thread from which the MessageBox is run has no active Window, as a consequence, the Owner is the Desktop (actually, IntPtr.Zero here).
This can be manually verified by activating (clicking on) the Form where the MessageBox is called: the MessageBox Window will disappear under the Form, since it's not owned by it.
How to solve:
Of course, using another Timer. The System.Windows.Forms.Timer (WinForms) or the DispatcherTimer (WPF) are the natural substitutes. Their events are raised in the UI Thread.
► The code presented here is just a WinForms implementation made to
reproduce a problem, hence these may not apply to all contexts.
Use a System.Timers.Timer: the SynchronizingObject property provides means to marshal the events back to the Thread that created the current class instance (same consideration in relation to the concrete implementation context).
Generate an AsyncOperation using the AsyncOperationManager.CreateOperation() method, then use a SendOrPostCallback delegate to let the AsyncOperation call the SynchronizationContext.Post() method (classic BackGroundWorker style).
BeginInvoke() the MessageBox, attaching it to the UI Thread SynchronizationContext. E.g.,:
this.BeginInvoke(new Action(() => MessageBox.Show(this, "Delivery Completed")));
Now the MessageBox is owned by the Form and it will behave as usual. The ThreadPool thread is free to continue: the Modal Window is synched with the UI Thread.
Avoid using a MessageBox for this kind of notifications, since it's really annoying :) There are many other ways to notify a User of status changes. The MessageBox is probably the less thoughtful.
To make them work as intended, without changing the current implementation, the Game and Form1 classes can be refactored like this:
class Game
{
private System.Threading.Timer deliveryTimer = null;
private int counter;
public event EventHandler DeliveryProgressChangedEvent;
public event EventHandler DeliveryCompletedEvent;
public Game(int eventsCount) { counter = eventsCount; }
public void StartDelivery() {
deliveryTimer = new System.Threading.Timer(MakeDelivery);
deliveryTimer.Change(1000, 1000);
}
public void StopDelivery() {
deliveryTimer?.Dispose();
deliveryTimer = null;
}
private void MakeDelivery(object state) {
if (deliveryTimer is null) return;
DeliveryProgressChangedEvent?.Invoke(this, EventArgs.Empty);
counter -= 1;
if (counter == 0) {
deliveryTimer?.Dispose();
deliveryTimer = null;
DeliveryCompletedEvent?.Invoke(this, EventArgs.Empty);
}
}
}
public partial class Form1 : Form
{
Game game = null;
public Form1() {
InitializeComponent();
pbDelivery.Maximum = 5;
game = new Game(pbDelivery.Maximum);
game.DeliveryProgressChangedEvent += onDeliveryProgressChanged;
game.DeliveryCompletedEvent += onDeliveryCompleted;
}
private void onDeliveryProgressChanged(object sender, EventArgs e)
{
this.BeginInvoke(new MethodInvoker(() => {
pbDelivery.Increment(1);
// This MessageBox is used to test the progression of the events and
// to verify that the Dialog is now modal to the owner Form.
// Of course it's not used in an actual implentation.
MessageBox.Show(this, "Delivery In progress");
}));
}
private void onDeliveryCompleted(object sender, EventArgs e)
{
this.BeginInvoke(new Action(() => MessageBox.Show(this, "Delivery Completed")));
}
private void button1_Click(object sender, EventArgs e)
{
game.StartDelivery();
}
}
Good information. The callback method executed by the timer should be reentrant, because it is called on ThreadPool threads. The callback can be executed simultaneously on two thread pool threads if the timer interval is less than the time required to execute the callback, or if all thread pool threads are in use and the callback is queued multiple times.
I have seen a lot of questions about how to edit controls on c# form from a different thread but none make much sense to me. I understand that you can not change any UI from another thread than it's main. To make this work you have to use invoke and from there safely edit the control?
I have a button that starts writing in a file and the moment you press the button the button itself gets disabled so you can not start multiple threads that do exactly the same. When the writing is done I want the button to be available again but I can not get it working on this other thread.
I have this as the Generate_Click event from the form.
private void Generate_Click(object sender, EventArgs e)
{
Generate.Enabled = false;
int x = 512;
int y = 512;
MBrot mbrot = new MBrot(x, y);
PB_Update lb = new PB_Update(0, y, Generator_PB, Generate, mbrot, this);
lb.Start();
}
And this is in PB_Update.cs the ThreadWork() function, when the while loop is done the writing to the file is done and so is the thread so its ended and given a messagebox with "finished" now as last the button needs to be enabled again.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.Enabled = true;
}
For WinForms you can execute directly on the thread which the control was created on through the Control.BeginInvoke method, you can use Control.Invoke as well but, Control.BeginInvoke is preferred for UI operations.
public void ThreadWork()
{
while (true)
{
if (currValue_ >= maxValue_)
break;
ThreadTick();
}
mb_.StopBrot();
t_.Interrupt();
MessageBox.Show("Finished!");
Generate_.BeginInvoke((Action)delegate()
{
Generate_.Enabled = true;
});
}
Somehow, get a reference to the form that hosts the generate_ button (let's call it myform). Then, at the bottom of your ThreadWork:
myform.Invoke(new Action(() => {
myform.SetGenerateEnabled();
}));
And then inside your form create that method that enables the button appropriately. (I used a method rather than just updating the button directly so that you don't publicly expose the button.)
This executes the commands inside the { ... } on myform's thread, which is a UI thread, because it is UI. At least, that's what I understand. This is how I do all of my UI updating from other threads.
Here's a simple example of a way to kick off an async task that disables a button for 5 seconds and then enables it again. Meanwhile, the rest of the UI is functional.
Note that this async method exists in the same class as your Generate_Click event, and runs on the UI thread. This means that it can enable and disable the button. But the long running task executes on a separate thread, so it doesn't lock the UI.
Hopefully this sample provides you a base to modify for your own code:
private void Generate_Click(object sender, EventArgs e)
{
DisableButton(sender as Button, 5);
}
private async void DisableButton(Button sender, int secondsToDisable)
{
sender.Enabled = false;
// In your code, you would kick off your long-running process here as a task
await Task.Run(()=>Thread.Sleep(TimeSpan.FromSeconds(secondsToDisable)));
sender.Enabled = true;
}
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 problem that I'd like some advice on. I have a button in my GUI that starts a complicated setup sequence (to connect to a analogue to digital converter and start logging data from an echo sounder). Once it is finished setting up, the button changes from START to STOP and has the expected behaviour. What I was experiencing is that during the long operation, if the user clicked on the button again (even though it was disabled) the event would still be sent to the button once it was reenabled. The only way I've found to make this work properly is to call Application.DoEvents() before enabling the button again. All I really want to do is swallow up the events destined for my button, so DoEvents() seems a bit heavy handed. Since people seem to be unanimously against calling DoEvents() I'm hoping that the bright minds here can help me come up with an alternative solution. Note I haven't tried my demo code but it follows my real code closely, excepting the really long methods.
Is there an alternative way to accomplish this?
Is it safe(ish) to call DoEvents() from the completion portion of the background worker?
public class Form1 : Form
{
BackgroundWorker worker;
Button startButton;
bool state;
public Form1() {
state = false;
BackgroundWorker worker = new BackgroundWorker();
worker.DoWork += new DoWorkEventHandler(StartSequence);
worker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ToggleButton);
startButton = new Button();
startButton.Text = "START";
startButton.Click += new System.EventHandler(StartClicked);
this.Controls.Add(startButton);
}
private void StartClicked( object sender, EventArgs e ) {
startButton.Enabled = false;
worker.RunWorkerAsync( !state );
}
private void StartSequence( object sender, DoWorkEventArgs e ) {
bool onState = (bool) e.Argument;
if ( onState ) {
RunReallyLongStartupSequence();
}
else {
RunReallyLongStopSequence();
}
state = onState;
}
private void ToggleButton( object sender, RunWorkerCompletedEventArgs e ) {
startButton.Text = state ? "STOP" : "START";
// THIS IS WHAT I AM WORRIED ABOUT!
Application.DoEvents();
startButton.Enabled = true;
}
}
Application.DoEvents() isn't heavy-handed, and I don't think programmers are unanimously opposed to DoEvents in all cases. DoEvents has a bad reputation because it has traditionally been used as a magical hack fix for badly-written code. In your case, it is the proper and normal way to deal with your situation.
In Windows, the situation you describe (where clicks on disabled buttons are applied when the buttons are re-enabled) is actually normal, expected behavior for a control. However, this does not mean that it is always desirable behavior from a programmer's standpoint, and if you have users that are prone to clicking away on disabled buttons, then the simplest way is to use DoEvents, which is nothing more than a method telling the form to go ahead and process any events it has queued up. Use it proudly!
First of all, I do not find DoEvents() bad at all, especially when it comes to gui, you can't imagine how many times it has helped me , and if you create things like progress bars, it is pretty much mandatory if you want to see it update, all I'm saying is that I don't really get why it is considered a bad command.
On the topic though, what I usually do in such situations is not disable the control at all
I use something similar to the following
public Class myForm :Form
{
private bool _working = false;
public myForm()
{
_working = true;
//code here
this.btnDoStuff.Click += new System.EventHandler(DoStuffClick);
_working = false;
}
private void DoStuffClick(object sender, EventArgs e)
{
if (_working) return;
_working = true;
DoStuff();
_working = false;
}
private void DoStuff()
{
//your code goes here
}
}
I find that the above helps me when it comes to allowing the user to do only one thing at a time, and if I want to be able to allow the user to do multiple stuff( for example while the command executes , to be able to press other buttons) I usually put the DoStuff() code to execute in a different thread
In case you were wandering, the reason I use an extra method (DoStuff()) to perform the actions, is that sometimes I need to execute code form other methods in one method, and if the _working flag is set to true, I can't call DoStuffClick(null , new EventArgs()) as it will not do anything
I hope I helped
p.s. yes I know it's been two years, but I only joined stackoverflow the other day (:
I am developing an application in C# using National Instruments Daqmx for performing measurements on certain hardware.
My setup consists of several detectors from which I have to get data during a set period of time, all the while updating my UI with this data.
public class APD : IDevice
{
// Some members and properties go here, removed for clarity.
public event EventHandler ErrorOccurred;
public event EventHandler NewCountsAvailable;
// Constructor
public APD(
string __sBoardID,
string __sPulseGenCtr,
string __sPulseGenTimeBase,
string __sPulseGenTrigger,
string __sAPDTTLCounter,
string __sAPDInputLine)
{
// Removed for clarity.
}
private void APDReadCallback(IAsyncResult __iaresResult)
{
try
{
if (this.m_daqtskRunningTask == __iaresResult.AsyncState)
{
// Get back the values read.
UInt32[] _ui32Values = this.m_rdrCountReader.EndReadMultiSampleUInt32(__iaresResult);
// Do some processing here!
if (NewCountsAvailable != null)
{
NewCountsAvailable(this, new EventArgs());
}
// Read again only if we did not yet read all pixels.
if (this.m_dTotalCountsRead != this.m_iPixelsToRead)
{
this.m_rdrCountReader.BeginReadMultiSampleUInt32(-1, this.m_acllbckCallback, this.m_daqtskAPDCount);
}
else
{
// Removed for clarity.
}
}
}
catch (DaqException exception)
{
// Removed for clarity.
}
}
private void SetupAPDCountAndTiming(double __dBinTimeMilisec, int __iSteps)
{
// Do some things to prepare hardware.
}
public void StartAPDAcquisition(double __dBinTimeMilisec, int __iSteps)
{
this.m_bIsDone = false;
// Prepare all necessary tasks.
this.SetupAPDCountAndTiming(__dBinTimeMilisec, __iSteps);
// Removed for clarity.
// Begin reading asynchronously on the task. We always read all available counts.
this.m_rdrCountReader.BeginReadMultiSampleUInt32(-1, this.m_acllbckCallback, this.m_daqtskAPDCount);
}
public void Stop()
{
// Removed for clarity.
}
}
The object representing the detector basically calls a BeginXXX operation with a callback that holds the EndXXX en also fires an event indicating data available.
I have up to 4 of these detector objects as members of my UI form. I call the Start() method on all of them in sequence to start my measurement. This works and the NewCountsAvailable event fires for all four of them.
Due to the nature of my implementation, the BeginXXX method is called on the UI thread and the Callback and the Event are also on this UI thread. Therefore I cannot use some kind of while loop inside my UI thread to constantly update my UI with the new data because the events constantly fire (I tried this). I also do not want to use some kind of UpdateUI() method in each of the four NewCountsAvailable eventhandlers since this will load my system too much.
Since I am new to threaded programming in C# I am now stuck;
1) What is the "proper" way to handle a situation like this?
2) Is my implementation of the detector object sound? Should I call the Start() methods on these four detector objects from yet another thread?
3) Could I use a timer to update my UI every few hundred miliseconds, irrespective of what the 4 detector objects are doing?
I really have no clue!
I'd use a simple deferred update system.
1) Worker threads signal "data ready" by raising an event
2) UI thread listens for the event. When it is received, it just sets a "data needs updating" flag and returns, so minimal processing occurs on the event itself.
3) UI thread uses a timer (or sits on Application.Idle events) to check the "data needs updating" flag and, if necessary, update the UI. In many cases, UI only needs to be updated once or twice a second, so this need not burn a lot of CPU time.
This allows the UI to continue running as normal all the time (remaining interactive for the user), but within a short period of some data being ready, it is displayed in the UI.
Additionally, and most importantly for good UI, this approach can be used to allow multiple "data ready" events to fire and be rolled into a single UI update. This means that if 10 pieces of data are completed in close succession, the UI updates once rather than your window flickering for several seconds as the UI redraws (unnecessarily) 10 times.
I would try moving the IDevice monitoring logic to seperate threads for each device. The UI can then poll for values via a timer event, button click or some other UI related event. That way your UI will remain responsive and your threads are doing all the heavy lifting. Here's a basic example of this using a continuous loop. Obviously, this is a brutally simple example.
public partial class Form1 : Form
{
int count;
Thread t = null;
public Form1()
{
InitializeComponent();
}
private void ProcessLogic()
{
//CPU intensive loop, if this were in the main thread
//UI hangs...
while (true)
{
count++;
}
}
private void Form1_Load(object sender, EventArgs e)
{
//Cannot directly call ProcessLogic, hangs UI thread.
//ProcessLogic();
//instead, run it in another thread and poll needed values
//see button1_Click
t = new Thread(ProcessLogic);
t.Start();
}
private void Form1_FormClosing(object sender, FormClosingEventArgs e)
{
t.Abort();
}
private void button1_Click(object sender, EventArgs e)
{
button1.Text = count.ToString();
}
}
Some updates to reflect the new data you've provided:
Although I have my doubts that your EndXXX methods are happening on the UI thread, I still think you should spawn off the work to a background thread and then update the UI either as events are fired or as needed.
Because you've added a tight while loop in your UI, you need to call Application.DoEvents to allow your other events to be called.
Here's an updated sample that shows results in the UI as they occur:
public class NewCountArgs : EventArgs
{
public NewCountArgs(int count)
{
Count = count;
}
public int Count
{
get; protected set;
}
}
public class ADP
{
public event EventHandler<NewCountArgs> NewCountsAvailable;
private double _interval;
private double _steps;
private Thread _backgroundThread;
public void StartAcquisition(double interval, double steps)
{
_interval = interval;
_steps = steps;
// other setup work
_backgroundThread = new Thread(new ThreadStart(StartBackgroundWork));
_backgroundThread.Start();
}
private void StartBackgroundWork()
{
// setup async calls on this thread
m_rdrCountReader.BeginReadMultiSampleUInt32(-1, Callback, _steps);
}
private void Callback(IAsyncResult result)
{
int counts = 0;
// read counts from result....
// raise event for caller
if (NewCountsAvailable != null)
{
NewCountsAvailable(this, new NewCountArgs(counts));
}
}
}
public class Form1 : Form
{
private ADP _adp1;
private TextBox txtOutput; // shows updates as they occur
delegate void SetCountDelegate(int count);
public Form1()
{
InitializeComponent(); // assume txtOutput initialized here
}
public void btnStart_Click(object sender, EventArgs e)
{
_adp1 = new ADP( .... );
_adp1.NewCountsAvailable += NewCountsAvailable;
_adp1.StartAcquisition(....);
while(!_adp1.IsDone)
{
Thread.Sleep(100);
// your NewCountsAvailable callbacks will queue up
// and will need to be processed
Application.DoEvents();
}
// final work here
}
// this event handler will be called from a background thread
private void NewCountsAvailable(object sender, NewCountArgs newCounts)
{
// don't update the UI here, let a thread-aware method do it
SetNewCounts(newCounts.Count);
}
private void SetNewCounts(int counts)
{
// if the current thread isn't the UI thread
if (txtOutput.IsInvokeRequired)
{
// create a delegate for this method and push it to the UI thread
SetCountDelegate d = new SetCountDelegate(SetNewCounts);
this.Invoke(d, new object[] { counts });
}
else
{
// update the UI
txtOutput.Text += String.Format("{0} - Count Value: {1}", DateTime.Now, counts);
}
}
}
I don't know if I fully understand. What if you update you an object that contains the current data. So the callback don't directly interact with the UI. Then you could update the UI at a fixed rate, e.g. n times per second from another thread. See this post on updating UI from a background thread. I am assuming that you are using Windows Forms and not WPF.
The B* * *dy captcha system decided it was a good idea to lose my answer I spent half an hour typing without so much as a warning or a chance to correct... so here we go again:
public class APD : IDevice
{
// Some members and properties go here, removed for clarity.
public event EventHandler ErrorOccurred;
public event EventHandler NewCountsAvailable;
public UInt32[] BufferedCounts
{
// Get for the _ui32Values returned by the EndReadMultiSampleUInt32()
// after they were appended to a list. BufferdCounts therefore supplies
// all values read during the experiment.
}
public bool IsDone
{
// This gets set when a preset number of counts is read by the hardware or when
// Stop() is called.
}
// Constructor
public APD( some parameters )
{
// Removed for clarity.
}
private void APDReadCallback(IAsyncResult __iaresResult)
{
try
{
if (this.m_daqtskRunningTask == __iaresResult.AsyncState)
{
// Get back the values read.
UInt32[] _ui32Values = this.m_rdrCountReader.EndReadMultiSampleUInt32(__iaresResult);
// Do some processing here!
if (NewCountsAvailable != null)
{
NewCountsAvailable(this, new EventArgs());
}
// Read again only if we did not yet read all pixels.
if (this.m_dTotalCountsRead != this.m_iPixelsToRead)
{
this.m_rdrCountReader.BeginReadMultiSampleUInt32(-1, this.m_acllbckCallback, this.m_daqtskAPDCount);
}
else
{
// Removed for clarity.
}
}
}
catch (DaqException exception)
{
// Removed for clarity.
}
}
private void SetupAPDCountAndTiming(double __dBinTimeMilisec, int __iSteps)
{
// Do some things to prepare hardware.
}
public void StartAPDAcquisition(double __dBinTimeMilisec, int __iSteps)
{
this.m_bIsDone = false;
// Prepare all necessary tasks.
this.SetupAPDCountAndTiming(__dBinTimeMilisec, __iSteps);
// Removed for clarity.
// Begin reading asynchronously on the task. We always read all available counts.
this.m_rdrCountReader.BeginReadMultiSampleUInt32(-1, this.m_acllbckCallback, this.m_daqtskAPDCount);
}
public void Stop()
{
// Removed for clarity.
}
}
Note I added some things I mistakenly left out in the original post.
Now on my form I have code like this;
public partial class Form1 : Form
{
private APD m_APD1;
private APD m_APD2;
private APD m_APD3;
private APD m_APD4;
private DataDocument m_Document;
public Form1()
{
InitializeComponent();
}
private void Button1_Click()
{
this.m_APD1 = new APD( ... ); // times four for all APD's
this.m_APD1.NewCountsAvailable += new EventHandler(m_APD1_NewCountsAvailable); // times 4 again...
this.m_APD1.StartAPDAcquisition( ... );
this.m_APD2.StartAPDAcquisition( ... );
this.m_APD3.StartAPDAcquisition( ... );
this.m_APD4.StartAPDAcquisition( ... );
while (!this.m_APD1.IsDone) // Actually I have to check all 4
{
Thread.Sleep(200);
UpdateUI();
}
// Some more code after the measurement is done.
}
private void m_APD1_NewCountsAvailable(object sender, EventArgs e)
{
this.m_document.Append(this.m_APD1.BufferedCounts);
}
private void UpdateUI()
{
// use the data contained in this.m_Document to fill the UI.
}
}
phew, I hope I dod not forget anything yping this a second time (that'll teach me not copying it before hitting Post).
What I see running this code is that;
1) The APD object works as advertised, it measures.
2) The NewCountsAvailable events fire and their handlers get executed
3) APD.StartAPDAcquisition() is called on the UI thread. Thus also BeginXXX is called on this thread. Therefore, by design, the callback is also on this thread and obviously also the NewCountsAvailable eventhandlers run on the UI thread. The only thing that is not on the UI thread is waiting for the hardware to return values to the BeginXXX EndXXX pair of calls.
4) Because the NewCountsAvailable events fire quite a lot, the while loop I intended to use for updating the UI does not run. Typically it runs once in the beginning and then somehow gets interupted by the eventhandlers that need to process. I do not fully understand this though, but it does not work...
I was thinking to solve this by getting rid of the while loop and putting a Forms.Timer on the form where UpdateUI() would be called from the Tick eventhandler. However, I do not know if this would be deemed "best practice". I also do not know if all these eventhandlers will eventually bring the UI thread to a crawl, I might need to add a few more of these APD objects in the future. Also UpdateUI() might contain some heavier code for calculating an image based on the values in m_Document. So the tick eventhandler might also be a resource drain in the timer approach. In case I use this solution I would also need to have a "Done" event in my APD class to notify when each APD finishes.
Should I perhaps not be working with events for notifying that new counts are available but instead work with some kind of "on demand" reading of APD.BufferedCounts and put the whole thing in yet another thread? I really haven't a clue...
I basically need a clean, lightweight solution that scales well should I add yet more APD's :)