So Ive been at it for some time.
Reaching the end of the stream the program freezes till there is new input only to freeze again.
Using a thread I get the exception that I cant access the Textbox from that thread. backgroundworker just doesnt do anything.
Im really kinda lost here
my code kinda looks like this
in the class that manages the tcp stream:
string readMessage()
{
return inputStream.ReadLine()
}
in my windows form class I need to somehow keep updating the textbox with the input stream without freezing the ui
public string readMessage()
{
string message;
message = inputStream.ReadLine();
return message;
}
that is what handles the tcp stream
now in my main form class it looks like this:
void Refresh(object sender, EventArgs e)
{
Thread.Sleep(1000);
n+=1;
label1.Text = Convert.ToString(n);
}
public void update()
{
Channel.AppendText(irc.readMessage());
}
void WORK()
{
while(true)
{
Channel.Invoke(new MethodInvoker(() => update()));
}
}
Work is the method called by the thread, while refresh is a method called by a timer that periodically sets the main thread to rest so the other thread can do it's work
that got it to run... until it reaches the end of the current stream and freezes again, until there is a new input in the stream which it quickly writes to the textbox and freezez again, which is exactly what I don't want.
edit: I managed to get it to work with the tcp.SenderTimeout, setting the timeout to a few ms and only updating the chat once a second but it still feels really unresponsive.
Is there a way to make it run smoother in the background? since Ill also have to work and respond to the input at runtime.
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 developing a user interface for a program, and something very strange is happening.
I have a text view, 2 buttons and a progress bar. I redirectioned the output on the console to my text view. so wen I click the buttons I should receive output messages. in the beginning it was fine, but then I used some longer routines, I'm trying to log in into a web service and use web-requests.
my code works almost as It was supposed to work, I can log in and make my web requests just fine. but because the answers can become slow I created some output messages, and there my problem started.. My interface wont update until all the code I created on my event handler end's running. and when that code ends executing, I receive all the output messages all at once. I cant even move the window while the program is running..
I´m programing on c# for my first time, I had to use it because I need to use dll's.. and this kind of problem never happened before. I usually use Java.
It's like the code isn't running on the right order and it doesn´t make sense to me.. because I know my code is right because it runs on the console, and it runs while the program isn't responding..
I cant seem to understand this, should I make my events handling using threads?
class MainClass
{
public static void Main (string[] args)
{
Application.Init ();
UIMain win = new UIMain ();
win.ShowAll ();
Application.Run ();
}
}
public partial class UIMain : Gtk.Window
{
public UIMain () :
base (Gtk.WindowType.Toplevel)
{
System.Windows.Forms.Application.EnableVisualStyles ();
this.Build ();
Console.SetOut (new ControlWritter(this.textview1));
}
protected void OnButton2Clicked (object sender, EventArgs e)
{
if (entry1.Text.Equals(String.Empty) || entry2.Text.Equals(String.Empty)) {
Console.WriteLine("random output");
}
ConstantesSetup.autoSetup ();
button1.Sensitive = true;
if (!ConstantesSetup.var1) {
ConstantesSetup.routine6 ();
ConstantesSetup.routine5 ();
ConstantesSetup.routine4 ();
ConstantesSetup.routine3 ();
ConstantesSetup.routine2 ();
ConstantesSetup.var1 = true;
}
}
protected void OnButton1Clicked (object sender, EventArgs e)
{
switch (ConstantesSetup.erp) {
case "ERP":
eti_scp.autoSync (this);
break;
}
}
}
I'm sorry for the lack of code, but I don't even know were to start looking for the problem..
thanks for your time ;)
You are blocking the UI thread with long running synchronous operations. You need to run these long running operations asynchronously so that the button click event handler can return right away while your tasks run in the background.
There are several options for running tasks asynchronously but one simple option is using a BackgroundWorker. In your event handler you could do something like:
var worker = new BackgroundWorker();
worker.DoWork += (o, args) =>
{
//call long running processes here
};
worker.RunWorkerAsync();
The BackgroundWorker will also dispatch these operations onto the UI thread for you so you can update controls in the form inside the DoWork callback method.
I have this form that spawns a new thread and starts listening and waiting for UDP packets in a loop. What I need is to keep the UI updated with the number of bytes received.
For that, I have setup an event which I'll raise as soon as a packet is received and pass the number of bytes received as an argument. Since I'm not running on the UI thread, I cannot simply update the UI directly. Here's what I'm currently doing:
private void EVENTHANDLER_UpdateTransferProgress(long receivedBytes) {
if(InvokeRequired) {
Invoke(new MethodInvoker(() => {
totalReceivedBytes += receivedBytes;
Label.Text = totalReceivedBytes.ToString("##,0");
}));
}
}
But this is still running on the same thread as the packet reception loop and it will not return to that loop - and wait for another packet - until this EVENTHANDLER_UpdateTransferProgress method returns.
My question is basically about the following line in the method above:
Label.Text = totalReceivedBytes.ToString("##,0");
Updating the UI like this slows down the packet reception. If I take that line off (or comment it), the packet reception will be much faster.
How can I possibly solve this issue? I think more threads is the key, but I'm not sure how to properly implement them in this situation... I'm using Windows Forms with .NET 2.0.
EDIT:
On my previous testing, the above seem to be true and it may actually be to some extent. But after a little more testing I realized the problem was on the whole Invoke(new MethodInvoker(() => { ... })); thing. When I remove that (the UI won't be updated of course) and leave EVENTHANDLER_UpdateTransferProgress but keep raising the event, the packet reception is much faster.
I tested receiving some file which took in average about ~1.5sec without calling Invoke() at all on the event handler. When I did call Invoke() in the event handler, even without updating any control in the UI or doing any operation (in other words, the anonymous method body was empty), it took much longer, around ~5.5sec. You can see it's a big difference.
Is there anyway to improve this?
The problem with your approach is that it updates the UI on every single packet. If you received 1000 packets every second, you would update the UI 1000 times every second! The monitor probably doesn't refresh more than 100 times per second, and nobody is going to be able to read it if it updates more than 10 times per second.
A better way to approach this problem is to put the totalReceivedBytes += receivedBytes; in the thread that handles the I/O and put a timer on the UI thread that executes Label.Text = totalReceivedBytes.ToString("##,0"); only a few times per second at most. When the transfer starts, start the timer; when the transfer stops, stop the timer.
Yes, there is a way to improve this.
The first is to use BeginInvoke instead of Invoke which will not wait for the invoke to return. You should also consider using another form in your method
private void EVENTHANDLER_UpdateTransferProgress(long receivedBytes) {
if(InvokeRequired) {
BeginInvoke(new Action<long>(EVENTHANDLER_UpdateTransferProgress),
receivedBytes));
return;
}
totalReceivedBytes += receivedBytes;
Label.Text = totalReceivedBytes.ToString("##,0");
}
So if you call this method from a method that does not require invoking, the update on the GUI is still performed.
Another option that you can do is break of a thread in your download thread. Something in the likes of
public event EventHandler<MonitorEventArgs> ReportProgress;
public void startSendingUpdates(MonitorEventArgs args) {
EventHandler<MonitorEventArgs> handler = ReportProgress;
if (handler == null) {
return;
}
ThreadPool.QueueUserWorkItem(delegate {
while (!args.Complete) {
handler(this, args);
Thread.Sleep(800);
}
});
}
public void download() {
MonitorEventArgs args = new MonitorEventArgs();
startSendingUpdates(args);
while (downloading) {
int read = downloadData(bytes);
args.BytesTransferred += read;
}
args.Complete = true;
}
public class MonitorEventArgs : EventArgs {
public bool Complete { get; set; }
public long BytesTransferred { get; set; }
}
The overhead of this is kind of small compared to the benefits. Your download thread is not affected by the updates to the GUI (at least not compared to waiting on the GUI to update). The downside is you are occupying a thread in the threadpool, but hey, that's what they're there for! And, the thread shuts down when it's done, since you set the complete flag. You don't need to lock when setting that either, since an extra run in the worker thread is unimportant in the context.
Have you tried using BeginInvoke instead of Invoke? BeginInvoke() is an asychronous call.
private void EVENTHANDLER_UpdateTransferProgress(long receivedBytes) {
if(InvokeRequired) {
BeginInvoke(new MethodInvoker(() => {
totalReceivedBytes += receivedBytes;
Label.Text = totalReceivedBytes.ToString("##,0");
}));
}
}
I have an application that imports data read from text files from a directory into a database. I have a UI that allows the user to click an import button and begin importing data and when the user clicks on that button again I wanted to stop importing the data in those files. I began using threads to allow this, so that I would not freeze up the UI while data was being imported. But Im having a few issues. I started using thread.Abort() to kill the thread after the user stops wanting to import but when the user clicks import again, some duplicate data is added to the database because it begins reading at the top of the text file which I dont want. I have been told to use ManualResetEvents and Thread.Join() to trigger for the import to finish, but im confused how that is supposed to work. Right now my code looks like this:
public ManualResetEvent event1 = new ManualResetEvent(false);
public Thread workerThread;
public Form1
{
InitializeComponent();
}
private void importButton_Click(object sender, EventArgs e)
{
if(importButton.Text == "Begin Import")
{
importButton.Text = "Stop Import";
//runs a service that begins reading and importing data and writing to
//a "console" box.
Service service = new Service(consoleBox);
//run the new thread to begin importing data
workerThread = new Thread(service.importData);
workerThread.Start();
}
else
{
importButton.Text = "Begin Import";
event1.Set();
while(!event1.WaitOne(TimeSpan.FromSeconds(4)))
{ //imports data for 30 more text files
service.importData(30);
workerThread.Join();
}
}
}
Basically what im trying to do is to keep the tread looping and checking to see if there is any files to be read, if there is then import The Data otherwise sleep for 4 seconds. Should I be using a threading Timer for this? I am a bit unsure of what to do.
Do not, in any way, block the UI thread by calling Thread.Join or ManualResetEvent.WaitOne. This will do exactly what you were trying to prevent; freeze the UI. Instead you need to create the MRE with its state initially set to true. Then in the importData method you need to periodically call WaitOne to see if importing should proceed (when the event is signaled) or pause (when the event is unsignaled).
Here is a rough sketch of how you would call WaitOne inside the importData method. Obviously, you would need to make adjustments to fit it into your specific implementation.
private void importData()
{
foreach (string filePath in GetFilesInSomeDirectory())
{
event1.WaitOne(); // Block when the MRE is unsignaled.
}
}
Then from your importButton.Click event handler you can call event1.Reset to pause the import operation or event1.Set to resume it.
Also, you should try to avoid calling Thread.Abort at all costs. It usually leads to more problems unless extra-special-nearly-impossible care is taken to avoid corrupting the state of the AppDomain.
Use timer for running the import process instead of thread, and define a variable to check if user request to stopinstead of thread.Abort() which by the way should be avoided.
In this code use System.Timers.Timer. and flag AutoReset property to false, so only import data if user not request to stop.
private System.Timers.Timer _importTimer = new System.Timers.Timer();
private volatile bool _requestStopImport = false;
public Form1()
{
InitializeComponent();
_importTimer.Interval = 4000;//4 seconds
_importTimer.AutoReset = false;//not automatically raise elapse event each time interval elapsed, only if we don't want to stop.
_importTimer.Elapsed += OnImportTimerElapced;
}
private void importButton_Click(object sender, EventArgs e)
{
if (importButton.Text == "Begin Import")
{
importButton.Text = "Stop Import";
StartImport();
}
else
{
importButton.Text = "Begin Import";
StopImport();
}
}
private void OnImportTimerElapced(object sender, System.Timers.TimerEventArgs e)
{
//runs a service that begins reading and importing data and writing to
//a "console" box.
Service service = new Service(consoleBox);//or maybe this would be a class level variable
service.importData();
if (!_requestStopImport)
{
_importTimer.Start();
}
}
private void StartImport()
{
_requestStopImport = false;
_importTimer.Start();
}
private void StopImport()
{
_requestStopImport = true;
_importTimer.Stop();
}
As you notice you don't have to use ManualResetEvent here. however if you want to be notified when code is completed or so you can use AutoResetEvent or raise an event for more detailed example check this.
I'm working on a card game in C# for a project on my Intro to OOP paper and have got the game working now but am adding "flair" to the GUI.
Currently cards are dealt and appear on the UI instantaneously. I want to have to program pause for a moment after dealing a card before it deals the next.
When a game is started the following code runs to populate the PictureBoxes that represent them (will be a loop eventually):
cardImage1.Image = playDeck.deal().show();
cardImage2.Image = playDeck.deal().show();
cardImage3.Image = playDeck.deal().show();
cardImage4.Image = playDeck.deal().show();
cardImage5.Image = playDeck.deal().show();
...
I have tries using System.Threading.Thread.Sleep(100); between each deal().show() and also inside each of those methods but all it achieves is locking up my GUI until all of the sleeps have processed then display all of the cards at once.
I have also tried using a combination of a timer and while loop but it resulted in the same effect.
What would be the best way of achieving the desired result?
The problem is that any code that you run on the UI will block the UI and freeze the program. When your code is running (even if it's running Thread.Sleep), messages (such as Paint or Click) sent to the UI will not be processed (until control returns to the message loop when you exit your event handler), causing it to freeze.
The best way to do this is to run on a background thread, and then Invoke to the UI thread between sleeps, like this:
//From the UI thread,
ThreadPool.QueueUserWorkItem(delegate {
//This code runs on a backround thread.
//It will not block the UI.
//However, you can't manipulate the UI from here.
//Instead, call Invoke.
Invoke(new Action(delegate { cardImage1.Image = playDeck.deal().show(); }));
Thread.Sleep(100);
Invoke(new Action(delegate { cardImage2.Image = playDeck.deal().show(); }));
Thread.Sleep(100);
Invoke(new Action(delegate { cardImage3.Image = playDeck.deal().show(); }));
Thread.Sleep(100);
//etc...
});
//The UI thread will continue while the delegate runs in the background.
Alternatively, you could make a timer and show each image in the next timer tick. If you use a timer, all you should do at the beginning is start the timer; don't wait for it or you'll introduce the same problem.
Normally I'd simply recommend a function like this to perform a pause while allowing the UI to be interactive.
private void InteractivePause(TimeSpan length)
{
DateTime start = DateTime.Now;
TimeSpan restTime = new TimeSpan(200000); // 20 milliseconds
while(true)
{
System.Windows.Forms.Application.DoEvents();
TimeSpan remainingTime = start.Add(length).Subtract(DateTime.Now);
if (remainingTime > restTime)
{
System.Diagnostics.Debug.WriteLine(string.Format("1: {0}", remainingTime));
// Wait an insignificant amount of time so that the
// CPU usage doesn't hit the roof while we wait.
System.Threading.Thread.Sleep(restTime);
}
else
{
System.Diagnostics.Debug.WriteLine(string.Format("2: {0}", remainingTime));
if (remainingTime.Ticks > 0)
System.Threading.Thread.Sleep(remainingTime);
break;
}
}
}
But there seems to be some complication in using such a solution when it is called from within an event handler such as a button click. I think the system wants the button click event handler to return before it will continue processing other events because if I try to click again while the event handler is still running, the button depresses again even though I'm trying to drag the form and not click on the button.
So here's my alternative. Add a timer to the form and create a dealer class to handle dealing with cards by interacting with that timer. Set the Interval property of the timer to match the interval at which you want cards to be dealt. Here's my sample code.
public partial class Form1 : Form
{
CardDealer dealer;
public Form1()
{
InitializeComponent();
dealer = new CardDealer(timer1);
}
private void button1_Click(object sender, EventArgs e)
{
dealer.QueueCard(img1, cardImage1);
dealer.QueueCard(img2, cardImage2);
dealer.QueueCard(img3, cardImage1);
}
}
class CardDealer
{
// A queue of pairs in which the first value represents
// the slot where the card will go, and the second is
// a reference to the image that will appear there.
Queue<KeyValuePair<Label, Image>> cardsToDeal;
System.Windows.Forms.Timer dealTimer;
public CardDealer(System.Windows.Forms.Timer dealTimer)
{
cardsToDeal = new Queue<KeyValuePair<Label, Image>>();
dealTimer.Tick += new EventHandler(dealTimer_Tick);
this.dealTimer = dealTimer;
}
void dealTimer_Tick(object sender, EventArgs e)
{
KeyValuePair<Label, Image> cardInfo = cardInfo = cardsToDeal.Dequeue();
cardInfo.Key.Image = cardInfo.Value;
if (cardsToDeal.Count <= 0)
dealTimer.Enabled = false;
}
public void QueueCard(Label slot, Image card)
{
cardsToDeal.Enqueue(new KeyValuePair<Label, Image>(slot, card));
dealTimer.Enabled = true;
}
}
The cheap way out would be to loop with calls to Application.DoEvents() but a better alternative would be to set a System.Windows.Forms.Timer which you would stop after the first time it elapses. In either case you'll need some indicator to tell your UI event handlers to ignore input. You could even just use the timer.Enabled property for this purpose if it's simple enough.
I would try puting the code that deals the deck ( and calls Thread.Sleep) in another thread.