.NET Forum / Languages / Managed C++ / May 2005
Form freezing form
|
|
Thread rating:  |
Ioannis Vranos - 19 May 2005 10:00 GMT Why in this code the form *does not refresh* when it gets the focus/after some time?
#using <mscorlib.dll> #using <system.windows.forms.dll> #using <system.dll> #using <system.drawing.dll>
#include <windows.h> #include <cstdlib>
// ==> Just a Form with two labels, one progress bar, opacity 90%. // ==> label1, and progressBar are public and accessed through main(). // ==> Placed some refreshes but the problem persists.
__gc class Form1: public System::Windows::Forms::Form { private: System::Windows::Forms::Label * label2; public: System::Windows::Forms::ProgressBar * progressBar1; System::Windows::Forms::Label * label1; Form1() { this->label2 = new System::Windows::Forms::Label(); this->progressBar1 = new System::Windows::Forms::ProgressBar(); this->label1 = new System::Windows::Forms::Label(); this->label2 = new System::Windows::Forms::Label(); this->progressBar1 = new System::Windows::Forms::ProgressBar(); this->label1 = new System::Windows::Forms::Label(); this->SuspendLayout(); this->label2->Font = new System::Drawing::Font(S"Microsoft Sans Serif", 9.75F, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, (System::Byte)161); this->label2->Location = System::Drawing::Point(14, 16); this->label2->Name = S"label2"; this->label2->Size = System::Drawing::Size(264, 40); this->label2->TabIndex = 1; this->label2->Text = S"Message 1..."; this->label2->TextAlign = System::Drawing::ContentAlignment::MiddleCenter; this->progressBar1->Location = System::Drawing::Point(10, 176); this->progressBar1->Name = S"progressBar1"; this->progressBar1->Size = System::Drawing::Size(272, 23); this->progressBar1->TabIndex = 2; this->label1->Font = new System::Drawing::Font(S"Microsoft Sans Serif", 8.25F, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, (System::Byte)161); this->label1->Location = System::Drawing::Point(14, 80); this->label1->Name = S"label1"; this->label1->Size = System::Drawing::Size(264, 64); this->label1->TabIndex = 3; this->AutoScaleBaseSize = System::Drawing::Size(5, 13); this->ClientSize = System::Drawing::Size(292, 271); this->Controls->Add(this->label1); this->Controls->Add(this->progressBar1); this->Controls->Add(this->label2); this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::Fixed3D; this->Name = S"Form1"; this->Opacity = 0.9; this->ShowInTaskbar = false; this->Text = S"Some Application"; this->TopMost = true; this->ResumeLayout(false); } };
int main()try { Form1 *pForm1= __gc new Form1;
pForm1->Show(); pForm1->Refresh();
pForm1->label1->Text= "Diagnostic code"; pForm1->progressBar1->Value= 0; pForm1->progressBar1->Maximum= 100;
while(true) { for(long i= 0; i< 100; ++i) { pForm1->progressBar1->Increment(1); pForm1->Refresh(); }
pForm1->progressBar1->Value= 0; }
}
catch(System::Exception *pe) { using namespace System;
Console::WriteLine("Error: {0}", pe->Message);
return EXIT_FAILURE; }
Tamas Demjen - 19 May 2005 18:15 GMT How about calling Application::DoEvents() instead of pForm1->Refresh()? DoEvents processes all messages in the message queue and returns. It's not nearly as good as multithreading, but it's very simple and prevents your application from freezing while you are processing something that lasts more than a 100 milliseconds. Call DoEvents periodically, but not too often, because it slows down your application.
> Why in this code the form *does not refresh* when it gets the > focus/after some time? [quoted text clipped - 4 lines] > pForm1->progressBar1->Increment(1); > pForm1->Refresh(); // <<<<<<<<<<<<<<<<<<<<< Application::DoEvents(); // <<<<<<<<<<<<<<<<<<<<<
> } > > pForm1->progressBar1->Value= 0; > } > > } Tom
Ioannis Vranos - 20 May 2005 01:13 GMT > How about calling Application::DoEvents() instead of pForm1->Refresh()? > DoEvents processes all messages in the message queue and returns. It's > not nearly as good as multithreading, but it's very simple and prevents > your application from freezing while you are processing something that > lasts more than a 100 milliseconds. Call DoEvents periodically, but not > too often, because it slows down your application. Thanks for the tip Tamas. It appears that it solves the problem, but another one exists. When I close the Form by pressing the X button, the application process does not terminate. Any ideas?
Here is the corrected code:
#using <mscorlib.dll> #using <system.windows.forms.dll> #using <system.dll> #using <system.drawing.dll>
#include <windows.h> #include <cstdlib>
// ==> Just a Form with two labels, one progress bar, opacity 90%. // ==> label1, and progressBar are public and accessed through main(). // ==> Placed some refreshes but the problem persists.
__gc class Form1: public System::Windows::Forms::Form { private: System::Windows::Forms::Label * label2;
public: System::Windows::Forms::ProgressBar * progressBar1; System::Windows::Forms::Label * label1;
Form1() { this->label2 = new System::Windows::Forms::Label(); this->progressBar1 = new System::Windows::Forms::ProgressBar(); this->label1 = new System::Windows::Forms::Label();
this->label2 = new System::Windows::Forms::Label(); this->progressBar1 = new System::Windows::Forms::ProgressBar(); this->label1 = new System::Windows::Forms::Label(); this->SuspendLayout();
this->label2->Font = new System::Drawing::Font(S"Microsoft Sans Serif", 9.75F, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, (System::Byte)161); this->label2->Location = System::Drawing::Point(14, 16); this->label2->Name = S"label2"; this->label2->Size = System::Drawing::Size(264, 40); this->label2->TabIndex = 1; this->label2->Text = S"Message 1..."; this->label2->TextAlign = System::Drawing::ContentAlignment::MiddleCenter;
this->progressBar1->Location = System::Drawing::Point(10, 176); this->progressBar1->Name = S"progressBar1"; this->progressBar1->Size = System::Drawing::Size(272, 23); this->progressBar1->TabIndex = 2;
this->label1->Font = new System::Drawing::Font(S"Microsoft Sans Serif", 8.25F, System::Drawing::FontStyle::Regular, System::Drawing::GraphicsUnit::Point, (System::Byte)161); this->label1->Location = System::Drawing::Point(14, 80); this->label1->Name = S"label1"; this->label1->Size = System::Drawing::Size(264, 64); this->label1->TabIndex = 3;
this->AutoScaleBaseSize = System::Drawing::Size(5, 13); this->ClientSize = System::Drawing::Size(292, 271); this->Controls->Add(this->label1); this->Controls->Add(this->progressBar1); this->Controls->Add(this->label2); this->FormBorderStyle = System::Windows::Forms::FormBorderStyle::Fixed3D;
this->Name = S"Form1"; this->Opacity = 0.9; this->ShowInTaskbar = false; this->Text = S"Some Application"; this->TopMost = true; this->ResumeLayout(false); } };
int main()try { using namespace System; using namespace System::Windows::Forms; using namespace System::Threading;
Form1 *pForm1= __gc new Form1;
pForm1->Show();
pForm1->label1->Text= "Diagnostic code"; pForm1->progressBar1->Value= 0; pForm1->progressBar1->Maximum= 100;
while(true) { for(long i= 0; i< 100; ++i) { pForm1->progressBar1->Increment(1);
if(i%10== 0) Application::DoEvents();
Thread::Sleep(100); }
pForm1->progressBar1->Value= 0; }
}
catch(System::Exception *pe) { using namespace System;
Console::WriteLine("Error: {0}", pe->Message);
return EXIT_FAILURE; }
Tamas Demjen - 20 May 2005 01:36 GMT > Thanks for the tip Tamas. It appears that it solves the problem, but > another one exists. When I close the Form by pressing the X button, the > application process does not terminate. Any ideas? If I'm not mistaken, your main function if in a forever loop. This is not typical in real-world applications. Usually your processing finishes sooner or later. If you want to be able to cancel a long lasting operation, you could have a Cancel button, which would set an IsCancelled flag on click. Your main loop could then check against this flag. This is how typical applications work.
I almost always perfer using threads, as opposed to DoEvents, because threads have automatic load balancing, and you don't have to figure out how often DoEvents should be called. It's also dangerous to call DoEvents, because it may call other functions you are not prepared for (it can have serious side effects, unwanted recursion, etc.).
When using DoEvents, your application won't be as smooth and responsive as using threads. You either call DoEvents too often, which slows down your processing, or you call it too rarely, which makes the GUI sluggish during processing. There is no such problem with threads, as the system scheduler is very smart, and you can even assign priorities to threads. Assigning low priority you can instruct the system to "do it if you have time, otherwise don't".
Tom
Willy Denoyette [MVP] - 20 May 2005 16:44 GMT You never give a chance to your application to handle the paint message posted to the application queue, your infinite loop takes all processing time. One bad advise would be to call DoEvents, but he! we are slowly moving to 64 bit OS'ses and this is a something that's been used on 16 bit non-preemptive windows. You should run your task (not really a realistic one) on another thread and update the UI from there using Control::Invoke or BeginInvoke.
Willy.
Willy.
> Why in this code the form *does not refresh* when it gets the focus/after > some time? [quoted text clipped - 102 lines] > return EXIT_FAILURE; > } Ioannis Vranos - 26 May 2005 16:44 GMT > You never give a chance to your application to handle the paint message > posted to the application queue, your infinite loop takes all processing [quoted text clipped - 4 lines] > You should run your task (not really a realistic one) on another thread and > update the UI from there using Control::Invoke or BeginInvoke. This is about a window displaying progress of file operations. I assume a form is the only thing that can be used as a "window". I want to display each file operation. Is your suggestion that I can't do much else than skipping to display all file operations, but instead display only from here and there?
The behaviour I am experiencing is exactly the same with this sample application, the form freezes, but the program itself (a method of the form calling a couple of other methods of the form) continues normally.
Ioannis Vranos - 26 May 2005 17:35 GMT > This is about a window displaying progress of file operations. I assume > a form is the only thing that can be used as a "window". I want to [quoted text clipped - 5 lines] > application, the form freezes, but the program itself (a method of the > form calling a couple of other methods of the form) continues normally. It seems that the problem got fixed after placing an Application::DoEvents() call at each iteration.
However if you have some more elegant idea, I would be glad to hear it (executing the file processing method in a separate thread and letting it modify the Form from there does not work, the program freezes).
Ioannis Vranos - 26 May 2005 17:44 GMT > It seems that the problem got fixed after placing an > Application::DoEvents() call at each iteration. > > However if you have some more elegant idea, I would be glad to hear it > (executing the file processing method in a separate thread and letting > it modify the Form from there does not work, the program freezes). you= you both, or someone else.
Tamas Demjen - 26 May 2005 18:04 GMT > However if you have some more elegant idea, I would be glad to hear it > (executing the file processing method in a separate thread and letting > it modify the Form from there does not work, the program freezes). Using a separate threads is the only reasonable and elegant solution. It's not terribly hard to do, but you really have to be careful. Writing thread safe code is hard, and if you miss something, it will freeze or crash.
I haven't tried threads in .NET/WinForms, but use them all the time in unmanaged applications, when a progress dialog is needed for a long-lasting operation.
When you update the form from the thread, are you taking care of the synchronization? Only the main thread is allowed to modify the GUI, you can't do it directly from the thread, you have to do the update via Control::Invoke.
There are tutorials out there, although few of them are C++, you can still get the point from them: http://samples.gotdotnet.com/quickstart/CompactFramework/doc/controlinvoker.aspx http://samples.gotdotnet.com/quickstart/howto/doc/WinForms/WinFormsThreadMarshal ling.aspx http://www.dotnetspider.com/technology/kbpages/903.aspx
Tom
Ioannis Vranos - 26 May 2005 18:58 GMT > When you update the form from the thread, are you taking care of the > synchronization? Only the main thread is allowed to modify the GUI, you > can't do it directly from the thread, you have to do the update via > Control::Invoke. Do you mean I should create a delegate for this? However my question is this, as far as I understand, before the Application::DoEvents() use, the form got "frozen" because of the frequency of the updates. Why this will not happen when Invoke() is used?
Willy Denoyette [MVP] - 26 May 2005 20:29 GMT >> When you update the form from the thread, are you taking care of the >> synchronization? Only the main thread is allowed to modify the GUI, you [quoted text clipped - 5 lines] > form got "frozen" because of the frequency of the updates. Why this will > not happen when Invoke() is used? Because your actual work is done on another thread and as such doesn't block the thread's message pump, the only thing what's done on the UI thread is updating the UI (paint, handle mouse moves/clicks, KB input etc...). Note that Control.Invoke and (preferable) BeginInvoke simply post a message (that basically contains the address of the function to execute) to the message queue, such that the function is executed on the UI thread (well the thread owning the Control).
Willy.
Ioannis Vranos - 30 May 2005 04:12 GMT > Because your actual work is done on another thread and as such doesn't block > the thread's message pump, the only thing what's done on the UI thread is [quoted text clipped - 3 lines] > message queue, such that the function is executed on the UI thread (well the > thread owning the Control). Apologies for bothering you with this, I am just considering if my code conversion to multithreading will offer any advantage over the Application::DoEvents().
The story is this, there is a file processing member function that executes as fast as possible, and the aim is the form displaying every file processed. That is, even in the case of BeginInvoke(), the form will be flooded with control changes (progress bar and label text), so I suppose two things will happen if I separate this in two threads. Either the form will not display all files but as much files as it will manage (= with delay?), or it will appear frozen again and thus Application::DoEvents() will be used to. I suppose in multicore CPUs the application will probably manage to draw everything in the case of two threads, however I am interested this to happen in single-core CPUs too.
So my question is this, since Application:DoEvents() does the job acceptably, should I stick to this or is it certain that placing the form in a separate thread of the file processing and "flooding" it with the same rate as before will have a different behaviour than before, and thus will make the use of Application::DoEvents() unnecessary?
Thank you all for the information that you have provided me so far.
Willy Denoyette [MVP] - 30 May 2005 23:34 GMT > So my question is this, since Application:DoEvents() does the job > acceptably, should I stick to this or is it certain that placing the form [quoted text clipped - 3 lines] > > Thank you all for the information that you have provided me so far. In my opinion you should forget about DoEvents and prefer background worker threads for non UI related processing. DoEvents is a hack invented for languages that don't support multithreading, running such applications will not take advantage of multicore CPU's at all.....
Now your question about "flooding the UI", all depends on how frequently you need/want to update the UI. I suppose you want to keep the impact of the file processing time by this UI update as small as possible, and you don't need to update the UI so frequently that the user can't visually see the difference between successive updates. Consider following small sample: The DoWork function runs on a background thread and reads a file (mscorlib.xml - 4790 KB) in chunks of 256 bytes, asynchronously updating the UI every 4096 bytes read. On a entry level box, this will take less than a couple of seconds when the file is not in the cache, and less than a second if the file is cached. During that time the UI will be updated 1196 times which is 1. way to fast for a good user experience. 2. UI thread and thread switching takes to much processing power So what you have to do is find an acceptable interval to update the UI, without Note that I'm not exactly processing the file data, so I need to give up my thread quantum each time I want to update the UI, in a real world scenario this may not be necessary as the scheduler will preempt to worker thread at regular intervals to let the UI thread process it's message queue. Compile and run the program, and play a bit with the frequency of updates to see exactly what I mean.
Willy.
#using <system.windows.forms.dll> #using <system.dll> #using <system.drawing.dll> using namespace System; using namespace System::IO; using namespace System::Text; using namespace System::Windows::Forms; using namespace System::Drawing; using namespace System::Threading; namespace Win { __delegate void StringParameterDelegate(String * s);
public __gc class Form1 : public Form { // Methods public: Form1() { InitializeComponent();} protected: virtual void Dispose(Boolean disposing) { __super::Dispose(disposing); } private: Button* button1; Label * cntText; System::ComponentModel::Container __gc * components; void InitializeComponent(void) { this->button1 = (new Button()); this->cntText = new Label(); this->SuspendLayout(); this->button1->Location = System::Drawing::Point(65, 25); this->button1->Size = System::Drawing::Size(60, 25); this->button1->TabIndex = 0; this->button1->Text = L"button1"; this->button1->Click += new System::EventHandler(this, &Form1::button1_Click); this->cntText->Location = System::Drawing::Point(5, 5); this->cntText->Size = System::Drawing::Size(185, 15); this->AutoScaleDimensions = System::Drawing::SizeF(6, 13); this->AutoScaleMode = System::Windows::Forms::AutoScaleMode::Font; this->ClientSize = System::Drawing::Size(200, 62); this->Controls->Add(this->button1); this->Controls->Add(this->cntText); this->Text = L"MThreaded Form"; this->ResumeLayout(false); } private: void button1_Click(System::Object* sender, System::EventArgs* e) { ThreadStart* myThreadDelegate = new ThreadStart(this, &Win::Form1::DoWork); Thread* t = new Thread(myThreadDelegate); t->IsBackground = true; t->Start(); } void DoWork() { FileStream* fs = File::OpenRead("C:\\WINDOWS\\Microsoft.NET\\Framework\\v1.1.4322\\mscorlib.xml"); String* status; int bytesRead = 0; int offset = 0; int count = 0; try { Byte b[] = new Byte[256]; UTF8Encoding* temp = new UTF8Encoding(true); while ((bytesRead = fs->Read(b, 0, b->Length)) > 0) { offset += bytesRead; if(offset % 4096 == 0) { status = String::Format(S"UI Thread accessed {0} times.", __box(count++)); UpdateUIStatus(status); Threading::Thread::Sleep(0); // Give other threads a chance to run by giving up our thread quantum } } } __finally { if (fs) __try_cast<IDisposable*>(fs)->Dispose(); } } void UpdateUIStatus(String *value) { // Are we running on the UI thread? if (InvokeRequired) { StringParameterDelegate *spd = new StringParameterDelegate(this, &Win::Form1::UpdateUIStatus); String *obj __gc[] = { value}; // We're not in the UI thread, so we need to call BeginInvoke BeginInvoke(spd, obj); return; } // Must be on the UI thread if we've got this far // Here we can safely touch the UI, or do anything the UI can do cntText->ForeColor = Color::Red; cntText->Text = value; } }; } using namespace Win; [STAThreadAttribute] int main() { Application::Run(new Form1()); return 0; }
Ioannis Vranos - 31 May 2005 00:51 GMT > In my opinion you should forget about DoEvents and prefer background worker > threads for non UI related processing. [quoted text clipped - 24 lines] > Compile and run the program, and play a bit with the frequency of updates to > see exactly what I mean. OK, thank you a lot for the information.
Free MagazinesGet these publications absolutely FREE for up to 12 months. There are no hidden fees and no obligation. Simply choose a title, complete the application form and submit it. Read more ...
|
|
|