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.
Related
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.
Following is a method that does Insert into the database table. And I am calling this method within DoWork() of BackGroundWorker thread. It obviously throws me the "cross thread operation not valid..." error. As I understand, I could use Invoke() method on UI controls if those to be accessed within DoWork(). BUT does it mean each of the following UI controls should have to be invoked? Is there a better way to achieve this?
private void AddToOccupations()
{
if (dataGridViewSearch.SelectedRows.Count > 0)
{
foreach (DataGridViewRow datarow in dataGridViewSearch.SelectedRows)
{
try
{
AllocationModel occupation = new AllocationModel()
{
User_ID = userID,
Merge_Status = (int)((MergeStatus)Enum.Parse(typeof(MergeStatus), cmbMergeStatus.SelectedItem.ToString())),
Start_Date = dateTimePickerFROMDate.Value,
Seat_Type = datarow.Cells[2].Value.ToString(),
Occupation_Status = cmbStatus_Type.SelectedItem.ToString(),
Session = datarow.Cells[3].Value.ToString(),
Seat_Number = (Int32)datarow.Cells[0].Value,
Number_of_Guests = (Int32)datarow.Cells[1].Value
};
// Call the service method
var success = this.allocationService.AddToOccupation(occupation);
if (success)
{
// display the message box
MessageBox.Show(
Resources.Add_Occupation_Success_Message,
Resources.Add_Occupation_Success_Title,
MessageBoxButtons.OK,
MessageBoxIcon.Information);
// Reset the screen
//this.Reset();
DoCurrentFreeSearch();
}
else
MessageBox.Show("No Records Inserted!");
}
catch (FormatException ex)
{
errorMessage = "Insert Error: \n";
errorMessage += ex.Message;
MessageBox.Show(errorMessage);
}
}
}
else
MessageBox.Show("Warning! No Row is selected...");
}
You should separate the worker code from the GUI code. Don't you have a DataSource on that data grid? You should basically first get the data you need from the grid (the selected rows) and pass them to the background worker from the GUI code (the button click or whatever). You can then report the work progress through BackgroundWorker.ReportProgress (which executes the progress event on the GUI thread).
If you can't decouple the GUI code from the worker code completely, you'll have to use Invokes for the rest. However, it's not clear from your code why you would need that, ReportProgress should be quite enough.
The general answer to your title question is: Yes.
If a background worker thread needs to access a UI item it can only do so by Invoking a method of it.
The problem of putting data into a DGV from a BW is best addressed by accessing its DataSource in a de-coupled way. I just did a little test to see if my suggestion from the original post works and it looks fine.
So, like with the Bitmap, you can create two DataSources as Properties; one to fill from the background worker and one to use as the DGV's Datasource.
public List<SeatData> theSeats_buffer { get; set; }
public List<SeatData> theSeats_DS { get; set; }
In the BW thread DoWork() you fill the theSeats_buffer list by calling an appropriate function void or bool getSeatData() and when you are done with the workload you pass the data into the theSeats_DS, maybe like this:
theSeats_DS= new List<Cols>(); theSeats_DS.AddRange(theSeats_buffer);
Again, this operation must be made thread-safe, probably by locking the receiving list theSeats_DS.
since the DataSource has been re-created it should be re-assigned in the bw_RunWorkerCompleted event; I did it right along with invalidating the display panel1:
private void bw_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
if ((e.Cancelled == true))
{ this.tbProgress.Text += "Cancelled!"; }
else if (!(e.Error == null))
{ this.tbProgress.Text += ("Error: " + e.Error.Message); }
else
{ panel1.Invalidate(); DGV.DataSource = theSeats_DS; }
}
With regard to the DB Inserts, I don't know how it relates. This answer is only about getting data from somewhere asynchrously and sending them to the UI.
Getting data from the DGV into the database is not something that would happen in the BW thread though, at least not the one that gets triggered upon incoming changes. If you use the DGV for input, concurrency will be an issue!! It is bad enough, if the seat one tries to reserve is actually taken by the time you press enter. But that can't be prevented. However, you need to ensure that the input isn't wiped out by incoming changes.. OTOH, a signal would be nice..
Concurrency is tricky!
Since the controls run on the UI thread, and not on the background worker thread, all of their functions need to be invoked.
I have a text block called "findListText". Here, I am updating the text in it:
private void InstantSearch(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
{
HitEnter = true;
}
findListText.Text = "Processing request. Please wait...";
Find(bool.Parse("False" as string));
}
However, the next set of code is a search function that can take up to 10 seconds, and at the end of it, it changes the text in findListText again.
private void Find(bool? bForward = true)
{
{
//Lots and lots of code
}
findListText.Text = "Search completed."
}
The problem is, the textblock never seems to update to "Processing request. Please wait...". The textblock is in it's original state and 10 seconds later updates to "Search completed.", seemingly skipping out the middle man.
I'm using C# - WPF. What am I doing wrong here?
Doesn't matter what technology I think.
The code is running on the same thread, meaning the the UI won't be updated untill all the code on that thread is completed. You should address a different thread to update that textblock.
In that case, you will have 2 thread:
The origininal thread, executing the "lots and lots of code"
The second (extra) created thread, which will handle updating the textblock's text while the other thread is executing the other code.
I've created a little something that should resolve your problem, it's based on this Stack Overflow page
Since this is WPF, try the following: after changing the text to "Processgin", call:
Application.Current.Dispatcher.Invoke(DispatcherPriority.Background, new Action(delegate { this.UpdateLayout(); }));
This will tell the thread to update the UI as soon as possible.
Here is how to run your find method in its own thread.
private void InstantSearch(object sender, KeyEventArgs e)
{
if (e.Key == Key.Return)
{
HitEnter = true;
}
findListText.Text = "Processing request. Please wait...";
BackgroundWorker tempWorker = new BackgroundWorker();
tempWorker.DoWork += delegate
{
Find(bool.Parse("False" as string));
};
tempWorker.RunWorkerAsync();
}
If you try that, you'll get an error because you access your UI thread from the background thread. So you'll need to update your find method as well.
private void Find(bool? bForward = true)
{
{
//Lots and lots of code
}
Dispatcher.BeginInvoke((Action) delegate {
findListText.Text = "Search completed."
});
}
You should look into the UI threading concept of WPF. Invoke the Dispatcher to modify the textbox. Also the search should run with ThreadPool.QueueWorkerItem.
// Start worker thread
ThreadPool.QueueUserWorkItem(state =>
{
// Long running logic here
findListText.Dispatcher.BeginInvoke(() => findListText.Text = "Processing request. Please wait...");
Find(bool.Parse("False" as string));
// Tip: Run change on GUI thread from the worker using the dispatcher
findListText.Dispatcher.BeginInvoke(() => findListText.Text = "Search completed.");
});
I came back to this just now, and had another browse across the internet for similar problems. For something as simple as pushing a single message before a long running process occurs, I'm surprised no-one suggested "Application.DoEvents();". Yes, I know it has it's flaws, and my UI will still hang, but this suits me perfectly for my situation, at least.
Using C# .NET 3.5.
It seems I'm not the only one, since a search seems to bring up a lot of similar questions, however reading through them I seem still to be missing the solution.
I'm raising an event with a button click on my UI, which is then supposed to start a backgroundWorker to get some time consuming work done - in this case, I want it to collect information from a form and a) write to an xml file as well as b) insert the information into a remote database.
This is exactly what the backgroundWorker was designed and intended to do I believe.
Here is the event raising code.
private void btnFinish_Click(object sender, EventArgs e)
{
clkFinish.setInstant();
curAct.setTimeFinish(DateTime.Now);
btnStart.Enabled = true;
bgwDataWorker.RunWorkerAsync();
for (int i = 0; i < 20; i++)
{
Console.WriteLine("Thread a: " + i);
Thread.Sleep(100);
if (i == (20 - 1))
{
Console.WriteLine("Finished");
}
}
}
As you can see, I have some code there which I've used as a counterbalance to the background worker code, which is here:
private void bgwDataWorker_DoWork(object sender, DoWorkEventArgs e)
{
Console.WriteLine("Running in a different thread now");
int count = 0;
for (int i = 0; i < 21; i++)
{
Console.WriteLine("Thread b: " + i);
Thread.Sleep(100);
(sender as BackgroundWorker).ReportProgress(5 * i, null);
if (i == (21 - 1))
{
count = i;
}
}
e.Result = count;
}
So far, everything seems to be working up to this point.
My problem is that when the code in the DoWork method is finished, nothing is happening. Here are the ProgressChanged and RunWorkerComplete methods.
private void bgwDataWorker_ProgressChanged(object sender, ProgressChangedEventArgs e)
{
Console.WriteLine(e.ProgressPercentage);
}
private void bgwDataWorker_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
{
Console.WriteLine("Result is " + e.Result.ToString());
}
This has me baffled.
I've tried running the examples from MSDN and I'm experiencing the same problems. RunWorkerCompleted and ReportProgress events are simply not being raised for some reason and I don't understand why.
Thanks for reading.
Alan
I had the same problem and I just figured out the answer.
In the properties of the backgroundworker, make sure that ProgressChanged and RunWorkerCompleted list the name of their functions (mine are called 'backgroundWorker1_ProgressChanged' and 'backgroundWorker1_RunWorkerCompleted' respectively).
You didn´t miss to set these variables, did you?
backgroundWorker1.WorkerReportsProgress = true;
backgroundWorker1.WorkerSupportsCancellation = true;
Also it could be an exception in the DoWork-Method, that isn´t handled.
Inksmithy, you mentioned you have renamed the BackgroundWorker. Please note that when you do this, the Forms Designer does NOT automatically change the reference to the old DoWork and RunWorkerCompleted methods. So in your Forms designer, click on your BackgroundWorker, then click on the Events icon (lightning bolt) in the Properties Window, and double-check that it is pointing to the correct Event Method Names.
Set WorkerReportsProgress property to True.
You've left out a vital bit - a boolean flag to allow it to raise the events
bgwDataWorker.WorkerReportsProgress = true;
bgwDataWorker.WorkerSupportsCancellation = true;
I had one project [A], (built a while back) that had the progress bar working. The current app [B] is not reporting progress. For some reason app [A] had the progress and work completed event handlers wired up. So, I hooked up the event handlers in app [B] and it is working. The handlers are copied into the designer.cs partial class file. (Assuming they could go in the form.cs partial class).
//
// backgroundWorker1
//
this.backgroundWorker1.WorkerReportsProgress = true;
this.backgroundWorker1.WorkerSupportsCancellation = true;
this.backgroundWorker1.DoWork += new System.ComponentModel.DoWorkEventHandler(this.backgroundWorker1_DoWork);
this.backgroundWorker1.ProgressChanged += new System.ComponentModel.ProgressChangedEventHandler(this.backgroundWorker1_ProgressChanged);
this.backgroundWorker1.RunWorkerCompleted += new System.ComponentModel.RunWorkerCompletedEventHandler(this.backgroundWorker1_RunWorkerCompleted);
//
hope this helps. Don't know why the app [A] was wired up by using the toolbox to drop the background worker on the form design window. Using VS2012 and Windows 7.
Also, I am calculating the progress percentage and for some reason the number of items in the listbox times the number of selected checkboxes exceeded 100% - it threw and error. So, I will cap the percentage at 99 and make that part of my logic for the progress percentage being reported back to the event.
I had this issue recently. It was caused because I had the main thread sleeping until the background thread was finished, which blocked the update from executing.
I solved this by wrapping my object (containing worker threads) into another thread so the progress update could execute, change the values in the main thread allowing my sleeping thread to continue.
string desktop = Environment.GetFolderPath(Environment.SpecialFolder.Desktop);
public void unzip(String zFile)
{
Ionic.Zip.ZipFile zip = Ionic.Zip.ZipFile.Read(zFile);
zip.ExtractProgress += new EventHandler<ExtractProgressEventArgs>(zip_ExtractProgress);
zip.ExtractAll(desktop + "\\cache", ExtractExistingFileAction.OverwriteSilently);
zip.Dispose();
zip = null;
}
void zip_ExtractProgress(object sender, ExtractProgressEventArgs e)
{
if (e.EventType == ZipProgressEventType.Extracting_EntryBytesWritten)
{
label2.Text = "debug: " + ((e.EntriesExtracted));
}
else if (e.EventType == ZipProgressEventType.Extracting_BeforeExtractEntry)
{
label3.Text = e.CurrentEntry.FileName;
}
}
private void button1_Click(object sender, EventArgs e)
{
unzip(desktop + "\\cache.zip");
}
When I execute the unzip button1_Click() my application freezes. I'm new to C# and I'm not really sure how to fix this, can someone help me?
Long running blocking operations should not run on the main UI thread, since as you can see the UI will freeze up.
Consider using a BackgroundWorker to do the work on a separate thread.
There's a good summary here.
Report progress back to the UI by handling the ProgressChanged event and calling backgroundWorker.ReportProgress(), rather than directly updating label2.Text from inside there.
i.e. from inside your zip_ExtractProgress method, call backgroundWorker.ReportProgress
label3.Text = e.CurrentEntry.FileName;
label3.Update();
The Update() method ensures that the label is painted, now showing the Text property you assigned. Without it, the painting doesn't happen until the unzipping code stops running and your program goes idle again. Otherwise known as 'pumping the message loop'. Calling Update() is only a partial fix, your window is still catatonic and won't respond to mouse clicks for example. If it takes longer than a couple of seconds, Windows displays the "Not responding" ghost window.
Get some experience with coding in C#, then tackle threading with the BackgroundWorker class.
Easiest way: Execute the unzip method using a BackgroundWorker. Be sure to modify GUI controls only on the main GUI thread, by using Invoke.