Stop Threads from spawning - c#

Having the Code Below in Windows forms.
private bool test = false;
private async void button1_Click(object sender, EventArgs e)
{
await Task.Run(() =>
{
test = !test ;
textBox2.Text = test.ToString(); // Each time i click text box switches to true and false
for (int i = 0; i < 1000000; i++)
{
textBox1.Text = i.ToString();
}
});
textBox2.Text = "Done"; // This will never happen until the task is done
}
If i Click button the textbox text Changes from 0 to 1000000.
Since i use async/await. the form will not freeze and I can see the Textbox Counting from 0 to 1000000.
But the problem is if i click the button again another thread spawn and textbox value changes by two threads. and you can see two counters from two threads changing textbox value.
if you click again you get 3 threads, 4 threads etc....Also textbox2 changes to true, false, true ....
This was just a test for me to see how actually async await works.
But i think im using it wrong. im afraid if i use async in my projects and end up to this situation.
How can i stop Threads spawning from single async method.
Currently I think each time i press the button New async Method spawns.
Here is What i see.

There's some confusion in the comments about what's happening. Especially since the posted code shouldn't actually work (it's not thread-safe).
The simplest way to reproduce this (drop a Button and a Label on a Form):
private async void button1_Click(object sender, EventArgs e) // add the async
{
for (int i = 0; i < 1000; i++)
{
label1.Text = "" + i;
await Task.Delay(100);
}
}
You can make this run multiple loops at once, you can increase the Delay to see that better. Note that there are no extra Threads involved, it all runs on the main GUI thread.
The keys here are async and await, they make the compiler turn a call to this method into a state machine and that way it can interleave the execution of multiple loops at once. Think of it as a method that can be paused at the await call and be resumed later. All on the same thread.
More importantly, it interleaves the loop(s) with the main thread so that that can continue to handle input messages and update the screen.
That was the 'why' part.
The how to solve it part depends on what you actually want, the now deleted answer from #CSharpie shows the basic pattern: use a boolean field as a guard, or disable & enable the Button.

Disable the button after pressed and and enable it when it finishes.

Related

WPF TextBlock shows all log messages only after all work is done

When user clicks on Execute button, I want to do some stuff and output log messages to TextBlock progressively - so user can see what is currently happening.
The problem is that my TextBlock changes it content after all work is finished (too late). How can I force WPF to repaint itself during process ?
Code looks like this:
private void btn_execute_Click(object sender, RoutedEventArgs e)
{
.... stuff ....
}
I have tried adding output_log.InvalidateVisual(); after any change to TextBlock, didn't work as expected.
If you run synchronous code in a Click handler of a Button, this code is being executed in the Dispatcher thread and thus prevents the Dispatcher from running any other code like displaying the changes of your messages in a TextBlock.
There are (at least) three possible ways to solve this issue.
First, you can run your Execute code in another Thread, Task or async event handler and set the Text using the Dispatcher:
private async void btn_execute_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 100; i++)
{
// Simulate doing some stuff...
await Task.Delay(100);
// Thanks to async/await the current context is captured and
// switches automatically back to the Dispatcher thread after the await.
output_log.Text += i + ", ";
// If you were using Task.Run() instead then you would have to invoke it manually.
// Dispatcher.Invoke(() => output_log.Text += i + ", ");
}
}
The main advantage is that you are not blocking the Dispatcher - which is highly recommended for everything you do.
Second, you can keep doing your Execute code in the Dispatcher, but then you have to "flush" the Dispatcher every time when you want to refresh your text, so that it can handle all waiting UI actions:
private void btn_execute_Click(object sender, RoutedEventArgs e)
{
for (int i = 0; i < 100; i++)
{
// Simulate doing some stuff...
Thread.Sleep(100);
output_log.Text += i + ", ";
Dispatcher.Invoke(DispatcherPriority.Background, new Action(() => { }));
}
}
This is certainly possible but I really wouldn't recommend it.
Or third,
you can use MVVM for your architecture,
run your Execute code in an async event handler (or Command),
change only the LogText property of your ViewModel and
use data binding to bind the TextBlock.Text to this MyLogViewModel.LogText property.
Unfortunately I can't give you a quick sample code for this scenario, but it's surely worth thinking about it because MVVM is just a really natural architecture for any kind of WPF application.

C# Invoke button control on separate thread

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;
}

C# Label Text Not Updating

I have the following code:
private void button1_Click(object sender, EventArgs e)
{
var answer =
MessageBox.Show(
"Do you wish to submit checked items to the ACH bank? \r\n\r\nOnly the items that are checked and have the status 'Entered' will be submitted.",
"Submit",
MessageBoxButtons.YesNoCancel, MessageBoxIcon.Question,
MessageBoxDefaultButton.Button1);
if (answer != DialogResult.Yes)
return;
button1.Enabled = false;
progressBar1.Maximum = dataGridView1.Rows.Count;
progressBar1.Minimum = 0;
progressBar1.Value = 0;
progressBar1.Step = 1;
foreach (DataGridViewRow row in dataGridView1.Rows)
{
if ((string) row.Cells["Status"].Value == "Entered")
{
progressBar1.PerformStep();
label_Message.Text = #"Sending " + row.Cells["Name"].Value + #" for $" + row.Cells["CheckAmount"].Value + #" to the bank.";
Thread.Sleep(2000);
}
}
label_Message.Text = #"Complete.";
button1.Enabled = true;
}
This is a test I am creating to port over to my application. Everything works fine but the label_Message.text being set. It never shows up on the screen. It is being set, I did a console.write on it to verify. It's just not refreshing the screen. I get the "Complete" at the end also.
Anyone have any ideas?
You're performing a lengthy operation on the UI thread. You should move it to a background thread (via BackgroundWorker for instance) so the UI thread can do things like repaint the screen when needed. You can cheat and execute Application.DoEvents, but I'd really recommend against it.
This question and answer are basically what you're asking:
Form Not Responding when any other operation performed in C#
The Label doesn't re-paint until you give the UI thread back to the message loop. Try Label.Refresh, or better yet, try putting your lengthy operation in a background thread as other posters have suggested.
This operation is executed in UI thread. UI won't update until it's finished. To make it update during sending you must perform sending in separate thread and update the label from there
This usually happens when you're doing intensive calculations/iterations in the same thread as where the user interface elements are running. To work around this you're going to need to have a separate thread do the work and from there update the label's value accordingly. I'd post a complete source sample but I'm away from my dev machine at the moment.
Just to add to this answer, I ran into an issue with our splash screen form.
We had code like this:
SplashScreen.Initialize(this, SplashScreenImage);
SplashScreen.Show();
// Db init, log init etc.
... Further in our app ...
Application.Run(new MainWindowForm());
The in Initialize(this, SplashScreenImage); we updated some controls and then we ran refresh on those controls;
public void Initialize(this, SplashScreenImage)
{
...
lblVersion.Text = GetVersionString();
lblEnv.Text = GetEnvironmentString();
// Refresh (does not work)
lblVersion.Refresh()
lblEnv.Refresh()
}
Unfortunately, this does not work. The problem here was that although we call control.Refresh() explicitly, form.show() was called after we called control.refresh. This does not work.
The fix was simple:
SplashScreen.Show(); // First call show
SplashScreen.Initialize(this, SplashScreenImage); // Now contorl.Refresh() works
I know this question is old but I had this same issue. I tried Refresh() and many other things but nothing worked. If I put the text into a Messagebox.show then it worked in the message box but not the form so I knew I had the data. As I had people waiting to use the app I was getting desperate and was just about to do away with the class temporarily to get it working when I thought of trying Invoke. So I tried
Invoke(new Action(() =>
{
lbltxt.Text = text;
}));
For now it works but still not sure if this is a long term fix or just a plaster till I find a better solution.

Thread issue in C#

I am trying to generate a random fruit and display it on GUI in a label. I am using this code to do it.
partial class Form1 : Form
{
int MagicNumber = 0;
List<string> NameList = new List<string>();
Random r = new Random();
public Form1()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
NameList.Add("Apples");
NameList.Add("Pears");
NameList.Add("Oranges");
NameList.Add("Bananas");
NameList.Add("Kiwi");
for (int i = 0; i < 8; i++)
{
Thread t = new Thread(new ThreadStart(Display));
t.Start();
label1.Text = NameList[MagicNumber];
Thread.Sleep(1000);
}
}
private void Display()
{
MagicNumber = r.Next(5);
}
}
The problem is the fact that in GUI i see only the last result of fruits choice and not how they are skipped from an iteration to other. I thought that this code will give me the possibility to see how fruits changes until the last was chosen , when i is 8.
Please if you have an idea why this code is not displaying how the fruits are chosen in label give me a hand !
Thanks.
You seem to be confusing timers and threads. In this case, I think what you want is a timer; specifically, System.Windows.Forms.Timer. You might do something like this:
partial class Form1 : Form
{
Timer timer = new Timer();
private void button1_Click(object sender, EventArgs e)
{
int i = 0;
timer.Tick += (s, e) =>
{
if (i < 8)
{
label1.Text = nameList[r.Next(5)];
i++;
}
else
timer.Stop();
};
timer.Interval = 1000;
timer.Start();
}
}
The idea is that you set a timer to tick once a second, and then each time it ticks, you change the label and increment the counter until it reaches 8 -- at which point it stops. You always want to make sure you call Start() after you've set Tick and Interval; otherwise, under some strange circumstances, the timer might tick before you have a chance to change the settings.
Alternatively, you could use threading and Sleep(), in which case it might look like this:
private void button1_Click(object sender, EventArgs e)
{
Thread t = new Thread(new ThreadStart(Display));
t.Start();
}
private void Display()
{
for(int i = 0; i < 8; i++)
{
label1.Text = NameList[r.Next(5)];
Thread.Sleep(1000);
}
}
Thread.Sleep() always sleeps the thread that it's called from -- so maybe this is what you meant to do.
However, this might throw a thread synchronization exception -- Forms prevents you from accessing UI controls from another thread, since it might be in an invalid state (i.e. in the middle of rendering or doing something else that's volatile). System.Windows.Forms.Timer actually runs on the UI thread, so it's easier to manage.
Your approach is flawed, but you may want to understand what is going on in your code, as it may help you find a better approach:
for (int i = 0; i < 8; i++)
{
Thread t = new Thread(new ThreadStart(Display));
t.Start();
label1.Text = NameList[MagicNumber];
Thread.Sleep(1000);
}
You are looking through, creating eight threads every time the button is clicked. Do you have a reason to create eight threads? If so, you may want to create them once, inside your init function and reuse them.
Then there is a race here in that your threads may not have had time to change MagicNumber before it is used, as the loop starts the threads then immediately changes the text, before going to sleep.
The sleep is another problem, as you haven't gotten off of the main (event) thread, so the text isn't changed until you exit that event handler.
If you want to see the text changing, then you will need to get off of the main thread, and in a second thread go through and do the loop of eight.
Then, you can put that thread to sleep, and since the main thread was free to make the change you will see it.
Here is an article from MS that is a bit dated, but the basic idea should help you:
http://msdn.microsoft.com/en-us/magazine/cc188732.aspx
Now you can use lambda expressions for your threads, as shown here:
http://www.rvenables.com/2009/01/threading-tips-and-tricks/
Just call Application.DoEvents(); after assigning text to label - that will refresh UI.
BTW I don't understand why you are using threads to generate random numbers
The problem is that when you execute an event handler or a function called from it, the changes are rendered at the end. Try changhing the label text inside the thread where you get the random number. You also have to set the CheckForIllegalCrossThreadCalls property to false in the form constructor.
Your observed problem of the form not refreshing is due to your function blocking the GUI thread and preventing a redraw of the window while its running. And it's continuously running for 8 seconds. The GUI thread needs to handle messages to allow a window to be redrawn.
But apart from what you observed it has has at least two theoretical problems related to threading:
The read of MagicNumber isn't volatile, so the compiler may read it only once and cache the result. It probably won't do that in practice since the code between each reading of the variable is so complicated that it can't guarantee that they won't affect the variable.
r.Next isn't threadsafe. So calling it from two different threads at the same time can corrupt the Random instance. Won't happen in practice either since the delay is so long that one thread will most likely have finished before the next one starts.
There is a much better way to choose a random item:
label1.Text = NameList.OrderBy(f => Guid.NewGuid()).First();
Randomizing on different threads is a bad idea in of itself.

Can BeginInvoke interrupt code already running on the UI thread?

Suppose I have some code which is running in the UI thread, which spawns a new thread to do work in the background, and then goes on to do UI work. When the background work is done, BeginInvoke is called from the new thread. If the UI work is still going on, will the callback given to BeginInvoke interrupt the UI work, or will it wait?
Code example - add a button called button1 to a form and add button1_Click to its click event, and then add this to the form:
bool continueWork = true;
private void button1_Click(object sender, EventArgs e)
{
Thread workThread = new Thread(performBackgroundWork);
workThread.IsBackground = true;
workThread.SetApartmentState(ApartmentState.STA);
workThread.Start();
for (long i = 0; i < long.MaxValue; i++)
{
if (!continueWork)
break;
button1.Text = i.ToString();
}
}
private void performBackgroundWork()
{
Thread.Sleep(1);
button1.BeginInvoke(new MethodInvoker(stopUIWork));
}
private void stopUIWork()
{
continueWork = false;
}
What is button1's text after it is clicked?
BeginInvoke adds the delegate to a queue (the message queue to be exact). So the answer is no, they won't get interrupted. Your button click handler is in fact executed due to a message in the message queue as well.
EDIT: Sorry, I forgot to answer your second question, but you can probably figure that out by yourself. The button text will be long.MaxValue - 1. You would probably want to execute stopUIWork without using BeginInvoke.

Categories

Resources