Explanation:
Structure: Windows form - three components: Text box, Text box Response and button.
Problem: I am moving motor with C# windows form: I am starting the motor and reversing
the direction of movement of motor with single button click with 10 second delay in the middle. i.e. I start the motor, have 10 sec delay and then reverse the motor. I want to display "Start" at the beginning and "End" at the end of the 10 second delay. I have tried using thread but it does not work. But I am only able to see "Finish" not "Start" in text box. The code is below:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace SampleThreadProgram
{
public partial class Form1 : Form
{
static EventWaitHandle _waitHandle = new AutoResetEvent(false);
delegate void SetTextCallback(string text);
void SetText(string text)
{
if (textBox.InvokeRequired)
{
SetTextCallback d = new SetTextCallback(SetText);
BeginInvoke(d, new object[] { text });
}
else
{
textBox.Text = text;
}
}
void UpdateTextBox(string message)
{
SetText(message);
_waitHandle.Set();
}
void Wait()
{
for (ulong i = 0; i < 10000; i++)
{
for (ulong j = 0; j < 100000; j++)
{
}
}
}
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
private void button1_Click(object sender, EventArgs e)
{
UpdateTextBox("Start");
_waitHandle.WaitOne();
Thread.Sleep(10000);
UpdateTextBox("Finish");
}
}
}
You should not use a big long for loop to make the computer wait for a while. Use Thread.Sleep at the very least.
You should use a BackgroundWorker to do what you're trying to do. Set the start text in the button click event, then start the background worker. You can have the DoWork event do some work (in this case sleeping) and use the WorkerCompleted event to update the UI.
The nice thing about using the background worker is that you don't need to worry about updating code form non-UI threads. In the button click event you can update the text of the textbox directly, and the BackGroundWorker thread will already ensure that the Completed event runs in the UI thread, so even there you can directly access UI controls. The BGW is specifically designed to make this exact case easier.
There's not enough time for the UI to update. Add an Application.DoEvents(); after the SetText(message); in UpdateTextBox.
Related
I created a C# project, a WinForms App with a background class. The WinForms App class contains a single point and draws this point everytime you call the "Refresh()" Method. With a press on the spacebar you get the background class running. The background class provides a "run()" method this method calls a calculation Method a specific number of times (while loop). the calculation method then performs some calculations and adds the results to a stack and then starts a timer. This timer contains a draw method, that takes the data from the stack and tells the Form to print a the circle for every stack entry. (Its quite confusing to describe code is below and should be easy to understand)
Now I got to a quite strange problem I don't understand:(see sample code below)
In the background class when the while loop calls the calculation the calculation will be done but the time will not execute the draw method. But when you comment out the while loop start and end and instead press the spacebar manually multiple times everything works fine. Why is the code working without the loop and manually pressing multiple times but not with the while loop? And how can I fix it to get it working with the loop?
Steps to reproduce the problem:
Create a new WinForms App in visual studio, replace the Form1.cs with the code below and create a BackgroundRunner.cs class with the code below.
Then simply run the project and press the spacebar to get the background class running.
To get the code working:
Just comment out the while loop start and end and press the spacebar manually multiple times. (the circle shout move)
Additional:
Yep in this broken down example it looks weird to create two seperate classes and a forms timer but in the whole project I need to get it working this way.
Thanks in advance.
I've already googled and found solutions like the timer and UI need to be on the same thread etc. But I think both are on the same thread, because it works without the loop.
This is the Form1.cs class:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TestApp
{
public partial class Form1 : Form
{
private Point p;
private BackgroundRunner runner;
private int count = 0;
private int max = 10;
public Form1() {
InitializeComponent();
p = new Point(0, 0);
runner = new BackgroundRunner(this);
}
private void Form1_Load(object sender, EventArgs e) {
}
//Method to refresh the form (draws the single point)
override
protected void OnPaint(PaintEventArgs e) {
drawPoint(p, e.Graphics);
}
//Method to draw a point
private void drawPoint(Point p, Graphics g) {
Brush b = new SolidBrush(Color.Black);
g.FillEllipse(b, p.X, p.Y, 10, 10);
}
//when you press the space bar the runner will start
override
protected void OnKeyDown(KeyEventArgs e) {
if(e.KeyValue == 32) {
runner.run();
}
}
public int getCount() {
return count;
}
public int getMax() {
return max;
}
//sets point, refreshes the form and increases the count
public void setPoint(Point pnew) {
p = pnew;
Refresh();
count++;
}
}
}
And this is the BackgroundRunner.cs class:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace TestApp
{
class BackgroundRunner
{
private Form1 form;
private System.Windows.Forms.Timer timer;
private Stack<int> test;
private int max;
private int count;
public BackgroundRunner(Form1 formtemp) {
form = formtemp;
timer = new System.Windows.Forms.Timer();
timer.Interval = 100;
timer.Tick += drawMovement;
}
public void run() {
count = form.getCount();
max = form.getMax();
//Here happens the strange stuff:
//If you comment out the while loop start and end
//and press manually the space bar the circle will move
//from right to left an up to down.
//But if you use the while loop nothing will happen
//and the "drawMovement" Method will never be executed,
//because the message inside will never be written...
Console.WriteLine("Count: " + count + " Max: " + max);
while (count < max) {
Console.WriteLine("Calc Move");
calculateMovement();
count = form.getCount();
max = form.getMax();
Thread.Sleep(50);
}
}
//Just a method that adds data to the stack and then start the timer
private void calculateMovement() {
test = new Stack<int>(5);
test.Push(10);
test.Push(20);
test.Push(30);
test.Push(40);
test.Push(50);
timer.Start();
}
//Method that goes through the stack and updates
//the forms point incl refresh
private void drawMovement(object o, EventArgs e) {
Console.WriteLine("Draw Move");
if (test.Count <= 0) {
timer.Stop();
return;
}
int x = test.Pop();
int y = count;
form.setPoint(new System.Drawing.Point(x, y));
}
}
}
Never block the main thread where the message loop takes place.
The timer is triggered within the message dispathing, that is why your timer is not triggered while you are blocking the main thread by the loop and sleep.
Make the method async by changing the public void run() into public async void run()
Replace Threed.Sleep with await Task.Delay then the loop would work asynchronously.
This would probably throw Exceptions if your application targets .NET Framewok version 2.0 or later (surely), because it is not thread-safe.
To solve this, put all codes that access the UI controls (which could be simply where the exception is thrown from, just in case that you are not sure what they are.) into
form.Invoke(new Action(()=>{
//Statements to access the UI controls
}));
Correction
The await will return execution on the UI thread because of the SynchronizationContext it is posted to. So there won't be exception about crossthreading.
by J.vanLangen
Hello I have a button and when i click it it loads user control and it tooks a little time. I want to show gif while it is loading. How can i do it?
Edit: I want add usercontrol to panel on background worker.
panel3.Controls.Add(hesaplar.Instance);
hesaplar.Instance.Dock = DockStyle.Fill;
hesaplar.Instance.BringToFront();
If you want to show the user a .gif animation while loading you can use Multithreading for parallel executing and Action Delegate to invoke safe GUI updates. please see my comments inside the code example:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication21
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
// show gif on a different thread until LongTask finish executing
System.Threading.Thread waitThread = new System.Threading.Thread(ShowGif);
waitThread.Start();
// start a new thread to execute LongTask() parallel to the waitThread
System.Threading.Thread longTaskThread = new System.Threading.Thread(LongTask);
longTaskThread.Start();
}
// put the code of your task that you want to execute in the background in that method
private void LongTask()
{
// long load...
for (int i = 0; i < 10; i++)
{
System.Threading.Thread.Sleep(1000);
}
// the "long task" inside the method finished to execute - dispose safely the picturebox from the GUI
panel3.Invoke(new Action(pictureBox1.Dispose));
// add user control to panel3
panel3.Invoke(new Action(AddUserControl));
// promt to screen
label1.Invoke(new Action(NotifyTaskHasFinished));
}
private void AddUserControl()
{
UserControl1 uc = new UserControl1();
panel3.Controls.Add(uc);
}
private void ShowGif()
{
// use action delegate to update GUI changes from another thread
panel3.Invoke(new Action(AddGif));
}
private void AddGif()
{
// show .gif animation inside picturebox1
pictureBox1.Image = WindowsFormsApplication21.Properties.Resources.raw_demo;
}
private void NotifyTaskHasFinished()
{
label1.Text = "LongTask() method has finished and user control was loaded";
}
}
}
This question already has answers here:
Unresponsive UI when using BeginInvoke
(3 answers)
Closed 7 years ago.
I have written a program that counts and the value is represented in the label's text.
The process is started by clicking on the button.
When I start, UI freezes.
I want to solve it by delegate.
Where is my bug?
Code
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Windows.Forms;
namespace WindowsFormsApplication6
{
public delegate void MyDelegate();
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
public void DelegateMethod()
{
for (int i = 0; i < 9999999999999; i++)
{
label1.Text = i.ToString();
}
MessageBox.Show("OK");
}
private void button1_Click(object sender, EventArgs e)
{
BeginInvoke(new MyDelegate(DelegateMethod));
}
}
}
It was because your UI element is getting updated so frequently and it will keep doing until loop terminates, if you add Thread.Sleep() after every iteration you can see the behaviour different:
for (int i = 0; i < 9999999999999; i++)
{
label1.Text = i.ToString();
// for example delay 1 second
Thread.Sleep(1000);
}
A more better approach is to use async and await keywords introduced in c# which will do extra work in background, not on UI thread, currently all the processing is getting done on your UI thread, which is causing UI thread to get blocked.But in your case that will not make difference, because here the problem is updating UI very fast which causes it to freeze.
It blocks because you are running it on the same thread as the UI. Also, that many updates to a label will not exactly perform very well - even if being updated from another thread.
If you really want to do that, create a new thread/task and invoke the UI update properly from there.
Because you do it on the UI-Thread. Try Task.Run() instead of BeginInvoke().
I have a program which uses many threads, when one of the threads found an answer (I think the context doesn't really matter) - it announces it, and then the first thread I created calles a function in a user control class using Invoke.
I checked - and if I change any attributes in this function, I do not get the cross-thread operation. But this function starts a timer (System.Timers.Timer) -> so the function of the "Elapsed" event is called. Inside it I am trying to change an attribute, and that causes cross-thread operation. What am I doing wrong? Isn't it possible to have the invoked function calling another function and then change the attributes in there?
By the way, is it wrong to invoke functions using the delegate? I mean, having the delegate as an attribute of the class I need it in, and then using delegAttributeName.Invoke(parameters) - and not this.Invoke(new Delegate(), parameters);
Heres part of the code:
Thats is where I invoke the function:
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace Nim_Assignment_3
{
public delegate void drawDeleg(Color c, int amount, int rowNumber);
public partial class Nim : Form
{
private event drawDeleg myDrawDeleg;
private void CheckXor()
{
if (this.foundToPaint)
{
this.myDrawDeleg.Invoke(this.currentTurnColor, this.amountToPaint, this.rowToPaint);
this.drawWait.WaitOne();
this.foundToPaint = false;
if (this.currentTurnColor == Color.Blue)
this.currentTurnColor = Color.Red;
else
this.currentTurnColor = Color.Blue;
}
}
// the invoked function:
private void callFillPencils(Color c, int amount, int rowNumber)
{
this.rows[rowNumber].fillPencils(c, amount);
}
}
}
And this is the function that the invoked function is calling - and the one it calls (the timer-elapsed event function):
(fillPencils - the function that the invoked function in the Form class (Nim) is calling):
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Timers;
namespace Nim_Assignment_3
{
public partial class PencilsUC : UserControl
{
private PictureBox[] pencils;
public static Image grayPencil = new Bitmap("GrayPen.bmp"), bluePencil = new Bitmap("BluePen.bmp"), redPencil = new Bitmap("RedPen.bmp");
private int amountOfPencils, amountOfPencilsLeft, currIndex, currAmount;
private System.Timers.Timer timer;
private Color currColor;
public event FinishedDrawing drawFinishedDeleg;
public PencilsUC()
{
// intializing things in the constructor...
this.timer = new System.Timers.Timer();
this.timer.Interval = 100;
this.timer.Elapsed += new ElapsedEventHandler(timer_Tick);
}
public void timer_Tick(object sender, EventArgs e)
{
// THE THING THAT MAKES THE CROSS THREAD-OPERATION: THE LINE INSIDE THE "if"
if (this.currColor == Color.Blue)
pencils[currIndex--].Image = bluePencil;
else
pencils[currIndex--].Image = redPencil;
this.currAmount--;
if (this.currAmount == 0)
{
this.timer.Stop();
if (this.drawFinishedDeleg != null)
this.drawFinishedDeleg.Invoke(this, new EventArgs());
}
}
public void fillPencils(Color c, int amount)
{
MessageBox.Show("Hello");
this.currColor = c;
this.currAmount = amount;
this.timer.Start();
}
}
}
(THE CROSS THREAD OPERATION HAPPENS INSIDE THE TIMER_TICK FUNCTION)
I used the windows forms timer at first but for some reason it didn't get to the tick-event function (timer.Start() was called but I put a message box in the tick function and it didnt get in there so I changed it - I saw some answers that said it was better)
I would love some help, I am sorry for the long post, I just wanted to be as clear as I can...
Thanks a lot in advance! :)
Use a Windows.Forms.Timer instead of a System.Timers.Timer. (You'll need to change the names of a few properties/events, i.e. Tick instead of Elapsed, but it's straightforward enough.)
The Timer in the Form's namespace marshals the Tick event into the UI thread, unlike the systems timer which executes the event in a thread pool thread.
If you really prefer to use the system's timer, then you can set the SynchronizingObject to have it marshall it's event to the UI thread:
timer.SynchronizingObject = this;
Note that the UserControl is a synchronizable object.
You need to .Invoke onto the main thread to change any controls.
Image image;
if (this.currColor == Color.Blue)
image = bluePencil;
else
image = redPencil;
this.Invoke(new MethodInvoker(() => pencils[currIndex--].Image = image));
The => is the syntax for a lambda (called anonymous method in other languages). Think about it as a one-line function.
() => pencils[currIndex--].Image = image
is the same as:
void SetImage(Image image, ref int currIndex) {
pencils[currIndex--].Image = image;
}
MethodInvoker provides a simple delegate that is used to invoke a method with a void parameter list
The easiest fix as you already made code would be to set SynchronizingObject of the Timer to Form, so timer would run on UI thread.
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.Threading;
namespace testThreads
{
public partial class Form1 : Form
{
public Form1()
{
InitializeComponent();
}
private void Form1_Load(object sender, EventArgs e)
{
}
public void countToLots()
{
for (int i = 0; i < 10000000; i++)
{
textBox1.Text = "Counting to 10000000, value is " + i + Environment.NewLine;
}
}
public void countToZero()
{
for (int i = 10000000; i > 0; i--)
{
textBox2.Text = "Counting to 0, value is " + i + Environment.NewLine;
}
}
private void button1_Click(object sender, EventArgs e)
{
Thread countUp = new Thread(new ThreadStart(countToLots));
Thread countDown = new Thread(new ThreadStart(countToZero));
countUp.Start();
countDown.Start();
}
private void button2_Click(object sender, EventArgs e)
{
textBox3.Text = "Bobby bob bob " + Environment.NewLine;
}
}
}
I really need to try and get the hang of this - i just dont understand the theory behind why i get an error message. Could someone help me out please?
Cross-thread operation not valid:
Control 'textBox1' accessed from a
thread other than the thread it was
created on.
UI controls have "thread affinity"; they do not want to be touched by anything except the UI thread; that includes reading and writing properties. The assignment to .Text should be done from the UI thread, either by using Invoke, or BackgroundWorker.
For example:
public void countToLots()
{
for (int i = 0; i < 10000000; i++)
{
// running on bg thread
textBox1.Invoke((MethodInvoker) delegate {
// running on UI thread
textBox1.Text = "Counting to 10000000, value is "
+ i + Environment.NewLine;
});
// running on bg thread again
}
}
But note that this type of thread switching has overhead. You should not call back every iteration - you should (for example) update the UI every [n] iterations - in the above, every 10000 for example.
You cannot use a method or property of a Form control from a different thread than the thread that created (called new) the control.
To do that just do:
public void countToLots()
{
for (int i = 0; i < 10000000; i++)
{
SetText("Counting to 10000000, value is " + i + Environment.NewLine);
}
}
public void SetText(string text)
{
if (this.textBox1.InvokeRequired())
{
Action<string> auxDelegate = SetText;
this.BeginInvoke(auxDelegate,text);
}
else
{
this.textBox1.Text = text;
}
}
What the method is doing with the beginInvoke is just calling again the SetText method from the thread that created the control.
Ok, about the theory behind WHY controls have UI Thread affinity.
If you've programmed long enough you would remember the days when forms and rapid application development were not the standard. In those days just droping a control into a form was rare... everything was done by the old school.
Now, in windows, the "old school" way of doing things involved defining a WindowProc.
The WindowProc is a function which is invoked to handle application message (notice I say is, not was). This function runs on the main program thread and is in charge of handling every message the application receives, including user interface paint and refresh.
Nowadays all that is mostly automated so that when you create a form the code in charge of doing all the work is autogenerated and you don't have to worry about that... but it is still there.
Of course, if the thread in charge of drawing the user interface with all its controls, is the main thread, you'll see how changing things from another thread might disturb the application itself with race conditions and so on. In addition, since the UI handling is autogenerated you can't just put the synchronization mechanisms that you'll use with two standard threads because you only have access to code on one thread, yours, but not to the main windowproc callback.
In a way, what BeginInvoke will do for you is pass a message to the main thread telling it to kindly handle the delegate in her own context when the time is right, thus delegating the execution to the main thread.