How progress information is sent to the main thread?
Like that:
Code:
System::Void Form1::DoCountWorker(FileStream ^fsInfile,BackgroundWorker ^bw,DoWorkEventArgs ^e)
{
BinaryReader brInfile(fsInfile);
const int nBufferSize=512;
array<Byte> ^abBuffer;
int nPercent=0,nLastPercent=-1;
do {
abBuffer=brInfile.ReadBytes(nBufferSize);
for (int i=0;i<abBuffer->Length;++i)
++aulCounts[abBuffer[i]];
if ((nPercent=(100*fsInfile->Position)/fsInfile->Length)!=nLastPercent) {
bw->ReportProgress(nPercent);
nLastPercent=nPercent;
}
} while (!bw->CancellationPending && abBuffer->Length==nBufferSize);
brInfile.Close();
if (bw->CancellationPending) e->Cancel=true;
}
System::Void Form1::bwDoCount_ProgressChanged(System::Object^ sender, System::ComponentModel::ProgressChangedEventArgs^ e)
{
progress->Text=String::Format("Processing file... {0}%",e->ProgressPercentage);
progress->progressBar1->Value=e->ProgressPercentage;
}
Where progress is the progress dialog form object.
By debugging I found out that the bwDoCount_ProgressChanged() event handler runs in the context of the GUI thread, so IMO inter-thread communication is done internally by BackgroundWorker.
But the progress display doesn't cause the deadlock anyway, the problem was only exposed when I clicked the Cancel button. Cancellation is handled by this simple event handler in the progress dialog's form class:
Where bwParentWorker is a tracking handle to the BackgroundWorker object that gets passed (i.e. assigned) from the main form object to the progress dialog's form object.
Closing the progress dialog in case I did not click the cancel button is donne by the completion handler (and works):
Code:
System::Void Form1::bwDoCount_RunWorkerCompleted(System::Object^ sender, System::ComponentModel::RunWorkerCompletedEventArgs^ e)
{
if (e->Error) MessageBox::Show(this,e->Error->Message,"Error during file processing",MessageBoxButtons::OK,MessageBoxIcon::Error);
bCountCancelled=e->Cancelled;
if (progress->Visible) progress->Close(); // Progress dialog is still open: close it
Cursor->Current=csrSaved; // This has just been added in response to Arjay's latest post
ewhWorkerReallyFinished->Set();
}
But, as described above, this handler doesn't even get called when I click Cancel.
Last edited by Eri523; September 19th, 2010 at 07:04 AM.
It is not clear from your post what is bw->ReportProgress(nPercent) and how it calls bwDoCount_ProgressChanged. Some code is missing to understand this.
Anyway, comment off ReportProgress line and try to reproduce the cancelling problem: what happens?
And the event handler in turn gets the handle from the BackgroundWorker object itself.
Thus, ReportProgress() is the BackgroundWorker method as described on MSDN and bwDoCount_ProgressChanged() is the event handler called by the BackgroundWorker as in response to my call to that method.
Some code is missing to understand this.
As you can conclude from the above, I don't have that code either; MS has.
Anyway, comment off ReportProgress line and try to reproduce the cancelling problem: what happens?
Interesting thing to try. Result: same problem as before (assertion failure as shown in the screenshot in post #21, fired from within Form1::DoCount(FileStream ^)).
Is the attached project code up to date? If not, updated it and I'll take another look at it.
No. I'll attach an updated one to this post and remove the one from the previous post.
Please let me know if it contains any unwanted files, so I can adjust my clean-up settings.
As I already mentioned, the input file used for testing doesn't really matter. The Test.txt attached to the previous post was used to generate the screenshot, however.
This thread is really too long... The problem was that thread hangs, and now assertion failure. I am completely lost. Just make small threading exercise without BackgroundWorker, better in C#, and you will understand everything.
This thread is really too long... The problem was that thread hangs, and now assertion failure. I am completely lost.
Yes, well, the two are related, however: The assertion failure is caused by a wait on an EventWaitHandle that is supposed to be set by the BackgroundWorker (more particular: its RunWorkerCompleted() event handler) timing out.
The main problem was you were blocking the main UI thread by waiting on an event. I removed the DoEvent( ) hacks because they aren't necessary.
I also restructured the code to use a modeless progress form rather than the modal one.
I removed the Diagnostic timer methods. If you want to put them back in, put the start in the DoCount and the stop in the BackgroundCompleted handler.
Here's the updated code:
Code:
#include "stdafx.h"
#include "Form1.h"
using namespace MCppTest;
System::Void Form1::bnAbout_Click(System::Object^ sender, System::EventArgs^ e)
{
MessageBox::Show("Eri's erstes .NET-Programm! <puh>");
}
System::Void Form1::bnCount_Click(System::Object^ sender, System::EventArgs^ e)
{
if (openFileDialog1->ShowDialog(this)==System::Windows::Forms::DialogResult::OK)
{
DoCount((FileStream ^)openFileDialog1->OpenFile());
}
}
System::Void Form1::bnClose_Click(System::Object^ sender, System::EventArgs^ e)
{
Close();
}
System::Void Form1::DoCountWorker(FileStream ^fsInfile,BackgroundWorker ^bw,DoWorkEventArgs ^e)
{
BinaryReader brInfile(fsInfile);
const int nBufferSize=512;
array<Byte> ^abBuffer;
int nPercent = 0, nLastPercent = -1;
do
{
abBuffer = brInfile.ReadBytes( nBufferSize );
for ( int i = 0; i < abBuffer->Length; ++i )
++aulCounts[abBuffer[i]];
if ( ( nPercent = ( 100 * fsInfile->Position ) / fsInfile->Length ) != nLastPercent )
{
bw->ReportProgress( nPercent );
nLastPercent=nPercent;
System::Threading::Thread::Sleep( 250 ); // Slow the reading down so we can test it (remove for production)
}
}
while ( !bw->CancellationPending && abBuffer->Length == nBufferSize );
brInfile.Close();
fsInfile->Close( );
if (bw->CancellationPending)
e->Cancel = true;
}
System::Void Form1::DoCount(FileStream ^fsInfile)
{
// Disable the buttons
bnAbout->Enabled = false;
bnCount->Enabled = false;
bnClose->Enabled = false;
// Save off the file name
_fileName = fsInfile->Name;
// Reset the chart text
chart1->Titles[0]->Text = L"Byte frequency histogram";
// Zero out the chart data
Series ^serTarget = chart1->Series["Byte frequencies"];
serTarget->Points->Clear();
// Zero out the array
for ( int i=0; i < 256; ++i )
aulCounts[i]=0;
// Save off the current Cursor
_csrSaved = Cursor;
// Set the wait cursor on the main form (not the progress form)
Cursor = Cursors::WaitCursor;
// To enable calling back to the BackgroundWorker object
progress->bwParentWorker=bwDoCount;
progress->Show( this );
bwDoCount->RunWorkerAsync(fsInfile);
}
System::Void Form1::DoCount(String ^strInfile) {
DoCount(gcnew FileStream(strInfile,FileMode::Open,FileAccess::Read));
}
System::Void Form1::Form1_DragEnter(System::Object^ sender, System::Windows::Forms::DragEventArgs^ e)
{
if(e->Data->GetDataPresent(DataFormats::FileDrop))
e->Effect = DragDropEffects::All;
else
e->Effect = DragDropEffects::None;
}
System::Void Form1::Form1_DragDrop(System::Object^ sender, System::Windows::Forms::DragEventArgs^ e)
{
// Always use only the first file dropped and pass it to counting function:
DoCount(((array<String ^> ^) e->Data->GetData(DataFormats::FileDrop, false))[0]);
}
System::Void Form1::bwDoCount_DoWork(System::Object^ sender, System::ComponentModel::DoWorkEventArgs^ e)
{
DoCountWorker(safe_cast<FileStream ^>(e->Argument),dynamic_cast<BackgroundWorker ^>(sender),e);
}
System::Void Form1::bwDoCount_ProgressChanged(System::Object^ sender, System::ComponentModel::ProgressChangedEventArgs^ e)
{
progress->Text=String::Format("Processing file... {0}%", e->ProgressPercentage );
progress->progressBar1->Value = e->ProgressPercentage;
}
System::Void Form1::bwDoCount_RunWorkerCompleted(System::Object^ sender, System::ComponentModel::RunWorkerCompletedEventArgs^ e)
{
if (e->Error)
{
MessageBox::Show( this
, e->Error->Message
, "Error during file processing"
, MessageBoxButtons::OK
, MessageBoxIcon::Error );
}
// Progress dialog is still open: close it
if (progress->Visible)
progress->Hide();
if (!e->Cancelled)
{
chart1->Titles[0]->Text=String::Concat("Byte frequency histogram of ", _fileName);
Series ^serTarget=chart1->Series["Byte frequencies"];
for ( int i=0; i < 256; ++i )
{
serTarget->Points->AddXY( i, aulCounts[i] );
}
}
// Restore the cursor
Cursor = _csrSaved;
// Restore the buttons
bnAbout->Enabled = true;
bnCount->Enabled = true;
bnClose->Enabled = true;
}
Lastly, I'm a firm believer in the amount of whitespace used in formatting is directly proportional to the developer's ability to track down issues.
As such, I reformatted the code to add some whitespace.
Last edited by Arjay; September 21st, 2010 at 11:41 PM.
Thanks for correcting my code, Arjay. Great work, really invaluable! And your implementation is so pretty straightforward. I couldn't imagine that it could be done that simple.
After adding the two new members you introduced to the Form1 class and re-adding the instantiation of the Progress object in DoCount(FileStream ^) that got lost somehow it worked instantly. I then could remove three or so members from the Form1 class that were unused now.
Did you add the Sleep() call in the worker function because you didn't have a 731 MB file to test the app with (or have a system where even processing that doesn't take a notable amount of time)? I found out that I could do some debugging pretty well without it.
I think one of the reasons why I got stuck with my screwed-up code was that I was stubbornly clinging too much to the concept of the modal progress dialog and trying to keep most work out of the RunWorkerCompleted() handler.
I must admit my "whitespace allergy" is most likely one of these "bad habits are hard to break" kind of problems. I started programming on a system that had a text display of 64 x 16 characters (and a "graphics" resolution of 128 x 48 brick-size "pixels", bi-level). (Not counting my previous experiments with programmable calculators.) Looks like I somehow still are stuck with this way of viewing things...
Now that the program is running fine, I've got only one question remaining before finally marking the thread resolved: As I (think I've) understood it, all windows of one GUI thread share a single common message pump. So, if my progress dialog could process messages, why then couldn't the main form?
Ah, and... Of course you and Alex F are now credited in that app's About box.
Last edited by Eri523; September 22nd, 2010 at 10:39 PM.
After adding the two new members you introduced to the Form1 class and re-adding the instantiation of the Progress object in DoCount(FileStream ^) that got lost somehow it worked instantly. I then could remove three or so members from the Form1 class that were unused now.
I just renamed them by prepending an '_' to the existing variables. That way I could see identify them as a class field rather than a method param or local variable.
Originally Posted by Eri523
Did you add the Sleep() call in the worker function because you didn't have a 731 MB file to test the app with (or have a system where even processing that doesn't take a notable amount of time)? I found out that I could do some debugging pretty well without it.
I think I commented it, but you definitely want to remove the Sleep. I added that because the file I was working with didn't give me enough time to test the cancel and cursor functionality.
Originally Posted by Eri523
I think one of the reasons why I got stuck with my screwed-up code was that I was stubbornly clinging too much to the concept of the modal progress dialog and trying to keep most work out of the RunWorkerCompleted() handler.
Essentially with threading you need to do any UI work before the thread starts (like disable buttons) and do any 'post' thread operations (like reenabling buttons) in the backgroundCompleted handler.[
Originally Posted by Eri523
I must admit my "whitespace allergy" is most likely one of these "bad habits are hard to break" kind of problems. I started programming on a system that had a text display of 64 x 16 characters (and a "graphics" resolution of 128 x 48 brick-size "pixels", bi-level). (Not counting my previous experiments with programmable calculators.) Looks like I somehow still are stuck with this way of viewing things...
I really find code without whitespace very hard to read. I think that's why I missed that you were waiting on the event in the UI thread the first time around.
Originally Posted by Eri523
Now that the program is running fine, I've got only one question remaining before finally marking the thread resolved: As I (think I've) understood it, all windows of one GUI thread share a single common message pump. So, if my progress dialog could process messages, why then couldn't the main form?
The reason was (and probably why the Cursor didn't work as well) because you were waiting on the thread completed [manual or auto-reset] event in the main UI thread. When you wait on an event with WaitOne, it blocks the main thread and the message pump doesn't run. You kind of overcame this partially by putting in a few DoEvent calls. I think Alex and I told you that DoEvent wasn't required (and having it in there indicated a problem).
I tell you coding in Managed C++ is a bit of a pain compared to coding in C#. The syntax for variables and namespaces is goofy. Something like:
Code:
using System::Threading;
private: System::Threading::AutoResetEvent^ _completedEvent;
In C#, the equivalent is:
Code:
using System.Threading;
private AutoResetEvent _completedEvent;
C# looks cleaner because there isn't all that namespace tomfoolery.
I was surprised to find out that intellisense doesn't work in managed C++. When you understand what needs to be done, but don't know the exact syntax (like me being unfamiliar with Managed C++), intellisense is sure handy.
Lastly, managed C++ organizes the classes into the std .h/.cpp file format, but I have to tell you, I find the project is really cluttered with both these files. In C#, there is only one file (.cs) for both the declaration and implementation - it's like half as many files to keep track of.
As a long time C++ developer that works a lot in C#, I tend to notice these sorts of things when switching between C++ and C#.
* The Best Reasons to Target Windows 8
Learn some of the best reasons why you should seriously consider bringing your Android mobile development expertise to bear on the Windows 8 platform.