[RESOLVED] How to UseWaitCursor correctly - Page 2
CodeGuru Home VC++ / MFC / C++ .NET / C# Visual Basic VB Forums Developer.com
Page 2 of 3 FirstFirst 123 LastLast
Results 16 to 30 of 40

Thread: [RESOLVED] How to UseWaitCursor correctly

  1. #16
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,591

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Arjay View Post
    The screen shot doesn't already have a cancel button. What you have is a close button (i.e the 'X' button).
    Ah, yes, of course, sorry... The screen shot you are referring to is a few versions of the app back. I have attached a current one to this post. (I hope this doesn't look like image overkill la firoz.raj now. )

    You can disable the 'X' button. Not sure how off the top of my head, but I'll look in google in a minute.
    Sorry again: I just posted that I found the solution for that issue (including the solution itself) 11 minutes before your post I'm just replying to. Looks like we encountered some kind of synchronisation problem here. Maybe we should use some kind of semaphore or the like? I hope you read this one in time before you waste any time.

    The user prompt to verify canceling isn't like a "are you really sure?". It's pretty much the standard when cancelling a long running operation in this manner. If you don't have it, your users will be pissed when they thought the 'X' would merely hide the progress dialog.
    I 100% agree when it comes to the X button. But is this really appropriate (or even required) for a button explicitly labelled "Cancel"?
    Attached Images Attached Images  

  2. #17
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,358

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Eri523 View Post
    I 100% agree when it comes to the X button. But is this really appropriate (or even required) for a button explicitly labelled "Cancel"?
    It depends on the 'severity' of what happens if the user cancels. If the user can restart the operation easily enough and the operation doesn't take much time, then you don't need the "are you sure" check. On the other hand, if the operation is destructive or takes a lot of time to run, then you should have one.

  3. #18
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,591

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Arjay View Post
    It depends on the 'severity' of what happens if the user cancels. If the user can restart the operation easily enough and the operation doesn't take much time, then you don't need the "are you sure" check. On the other hand, if the operation is destructive or takes a lot of time to run, then you should have one.
    Ok, then I don't think I need this extra check. Processing can be restarted easily by just clicking a button and picking a file in a file open dialog. In the current version, processing of the 731 MB test file takes slightly below 30 seconds. It is faster now again, which is a side-effect of changing from synchronous to asynchronous file processing. But I don't think this is because the worker thread is that amazingly fast as-such, rather it is because I reduced the frequency of progress updates. And as I think I'm limited to a maximum file size of 2 GB anyway, total processing time can't grow that much.

    As a contrast, BTW, processing time for a small 6 kB file grew from about 15 ms to 250 ms.

    I started changing the program from synchroous to asynchronous processing more or less right after writing my post #14. I had to go some distance before I got to a state that I could compile and run, but than it worked almost instantly.

    ... as long as I didn't click the Cancel button. If I did that, I ran into a debug assertion. At least it was one that I placed in the program as a precaution myself. Secifically, it was the one on bwDoCount->IsBusy in Form1::DoCount(FileStream ^) (see below).

    The current version does an assertion failure on the line just above that, in the assertion on bEwhSignaled.

    EDIT: One really important thing I forgot to mention: To my big surprise, Form1::bwDoCount_RunWorkerCompleted() appears to not even get called at all when I click the Cancel button. Or, and that might be even a bit stranger, it actually is called, but the breakpoint I placed there has no effect.

    Actually I had to do some brain twisting to get the combination of the worker thread and the modal progress dialog to work at all, as I obviously can't do any work in the GUI thread as long as the progress dialog is open. Maybe the solution I came up with is a bit screwed up, though I don't hope so.

    I have been struggling with that a whole lot of time since then, and I can hardly believe that I started this just last sunday. It feels like ages...

    I did a lot of fiddling around with the code to no avail. Finally, I reconstructed a state that is close to the initial one and of which I think it's a reasonable point to start over.

    As I have run out of ideas now, I hope for some help from you guys around here. Below are the functions that I think are relevant. All variables that are not local are members of the respective classes. I hope they're self-explanatory enough, as I won't include all the declarations for now.

    These are the functions involved in the main form class (Form1):

    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::DoCount(FileStream ^fsInfile)
    {
      progress=gcnew Progress;
      ewhWorkerReallyFinished=gcnew EventWaitHandle(false,EventResetMode::AutoReset);
      UseWaitCursor=true;
      progress->bwParentWorker=bwDoCount;  // To enable calling back to the BackgroundWorker object
    #ifdef _DEBUG
      Diagnostics::Stopwatch stw;
      stw.Start();
    #endif
      for (int i=0;i<256;++i) aulCounts[i]=0;
    #ifdef _DEBUG
      Diagnostics::Debug::Assert(!bwDoCount->IsBusy,"Background worker already busy when going to start it");
    #endif
      bwDoCount->RunWorkerAsync(fsInfile);
      System::Windows::Forms::DialogResult dlgr=progress->ShowDialog(this);
      bool bEwhSignaled=ewhWorkerReallyFinished->WaitOne(60000);  // To be really sure!
    #ifdef _DEBUG
      Diagnostics::Debug::Assert(bEwhSignaled,"Wait for worker shut-down timed out");
      Diagnostics::Debug::Assert(!bwDoCount->IsBusy,"Background worker still running after cancellation");
    #endif
      fsInfile->Close();
    
      if (!bCountCancelled) {
        chart1->Titles[0]->Text=String::Concat("Byte frequency histogram of ",fsInfile->Name);
        Series ^serTarget=chart1->Series["Byte frequencies"];
        serTarget->Points->Clear();
        for (int i=0;i<256;++i) serTarget->Points->AddXY(i,aulCounts[i]);
      }
    #ifdef _DEBUG
      stw.Stop();
      Diagnostics::Debug::WriteLine("Elapsed time: {0} ms",stw.ElapsedMilliseconds);
    #endif
      UseWaitCursor=false;
      ewhWorkerReallyFinished=nullptr;
      progress=nullptr;
    }
    
    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}&#37;",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);
      bCountCancelled=e->Cancelled;
      if (progress->ContainsFocus) progress->Close();  // Progress dialog is still open: close it
      ewhWorkerReallyFinished->Set();
    }
    I hope this isn't too much code for now.

    There are some variables that just get assigned a function return value and then are never used again. These are for inspecting the return values in the debugger.

    The progress form class only contains a single function that I defined myself:

    Code:
    System::Void Progress::bnCancel_Click(System::Object^  sender, System::EventArgs^  e)
    {
      bwParentWorker->CancelAsync();
    }
    I can zip and upload the entire project as well, if someone should be interested in that.

    TIA
    Last edited by Eri523; August 31st, 2010 at 10:36 PM.

  4. #19
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,591

    Re: How to UseWaitCursor correctly

    Well, in the meantime I've gathered some more information about this issue. (But they're really few as I'm still short of ideas.)

    But first let me pick up the following statement from my own previous post:

    Quote Originally Posted by Eri523 View Post
    EDIT: One really important thing I forgot to mention: To my big surprise, Form1::bwDoCount_RunWorkerCompleted() appears to not even get called at all when I click the Cancel button. Or, and that might be even a bit stranger, it actually is called, but the breakpoint I placed there has no effect.
    I edited it into the middle of the post, two hours after doing the post itself. Maybe some people who have read the original post short time after I did it missed this one, as I placed it right in the middle of that really long post. ... really my fault...

    An update closely related to this one: Form1::bwDoCount_RunWorkerCompleted() actually is called if file processing is completed the normal way, i.e. I don't click the Cancel button. And this confirms, in addition, that the breakpoint I set in the ..._RunWorkerCpleted() handler is indeed effective. It only isn't called if I click the Cancel button.

    In the Fibonacci sample from MSDN, the completion function is called in both cases, like I (and probably everyone else) had expected. But the MSDN sample does invocation of the background worker, progress display and cancellation handling all within a single form, and thus was not really that good a model for what I'm doing in my app. (Actually, this was the reason for me having to do a lot of initial thinking before writing my code.)

    Another fact: Any of the BackgoundWorker event handlers (except ..._DoWork() of course) runs in the context of the thread owning the BackgroundWorker object, IOW the GUI thread. But that's exactly what I expected, and anyone who really knows about that subject would have known that even more likely.

    A funny side note at the end: If the MSDN docs were right, the debugger of the Express Edition wouldn't even have a Threads window. Luckily, this is not the case.

    Any ideas now?

    TIA

  5. #20
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,358

    Re: How to UseWaitCursor correctly

    Zip up the project and include any data files. Also include what is ocurring and what you expect.

  6. #21
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,591

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Arjay View Post
    Zip up the project and include any data files. Also include what is ocurring and what you expect.
    Thank you for your effort Arjay, both backward and forward in time.

    I have attached the zipped project in question. I did a clean-up on the project before zipping it, so I hope it doesn't contain any files that are unwanted here. If it does inspite of this, please tell me their extensions and I'll add them to my clean-up settings.

    I also added two screen shots. One is the outcome of processing the plain text file Test.txt that I attached as well, when I don't click the Cancel button. But that's not really the issue.

    The other screen shot shows the assertion failure I get when I actually click the Cancel button (after some waiting of course). Obviously, I can't attach the 731 MB file that gives me enough processing time to do so. But you can try any large file you want: The actual contents of the file doesn't really matter.

    Again: Really, really big TIA. I'm really out of ideas what might cause this misbehaviour.

    EDIT: Removed outdated attached project file. A new one is attached to post #35.
    Attached Images Attached Images   
    Attached Files Attached Files
    Last edited by Eri523; September 19th, 2010 at 06:28 PM. Reason: Removed attachment

  7. #22
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,591

    Re: How to UseWaitCursor correctly

    Ok, now that I'd waited 1+ week for getting additional insights from here without success, I resorted to a Google search for "BackgroundWorker RunWorkerCompleted not called". Although I didn't expect much from that because I interpreted the lack of resonance here as an indication for the problem not being really common, I found something (19.600 hits).

    This taught me at least three things: 1) The problem is not really that uncommon. 2) It is not limited to C++/CLI. 3) It is not new: The search hits date back to at least 2007.

    I found at least a partial solution at http://www.experts-exchange.com/Prog..._22354237.html. The user posting there, who was using VB .NET, first resorted to calling Application::DoEvents(). What I suppose to be the real solution was unfortunately blurred on that page and covered by a box suggesting to sign up.

    However, I tried adding a single (!) call to Application::DoEvents() before the line waiting for ewhWorkerReallyFinished to get signaled. (Those who didn't download the entire zipped project may have a look at the code excerpt in post #18.) And suddenly it worked like expected!

    Yes, I know that calling Application::DoEvents() is evil, and actually the introduction of the background worker was initially intended to remove just that. But it simply works...

    So now, can someone tell me why it works now and maybe how I could solve the problem in a more elegant or correct way?

    And there's another thing I've learned: People are always told on this site to first try a Google (or whatever) search. Maybe I myself should take that option into account more often...
    Last edited by Eri523; September 16th, 2010 at 10:33 PM.

  8. #23
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,358

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Eri523 View Post
    And there's another thing I've learned: People are always told on this site to first try a Google (or whatever) search. Maybe I myself should take that option into account more often...
    That should always be your first attempt at solving the problem. More often than not, you can find the answer yourself.

    I haven't looked at your code because I don't have VS2010 handy right now. If adding a DoEvents call causes your program to work, whatever you're doing is simply causing the UI thread not to be able to pump messages.

  9. #24
    Join Date
    Jul 2002
    Posts
    2,516

    Re: How to UseWaitCursor correctly

    This thread is too long, and has too much code, I don't think that anyone really reads this code. However, the last post about DoEvents gives me some idea. There is classic deadlock situation with a worker thread. Consider working thread sending synchronous (Invoke) messages to the main thread. At some point main thread starts to wait for worker thread end in wait operation, this means, it becomes unresponsive and doesn't handle any messages, including Invoke. At the same time, worker thread calls Invoke: deadlock. DoEvents in the main thread solves the problem, handling Invoke call and releasing the worker thread.
    This situation is described here: http://www.codeproject.com/KB/cs/workerthread.aspx
    Use BeginInvoke instead of Invoke, and this solves the problem. Generally, asynchronous call must be always default choice vs synchronous.
    Also, it is a good idea to create thread manually at least once, instead of using BackgroundWorker. This allows to understand what really happens in multithreaded program. BackgroundWorker hides important implementation details.

  10. #25
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,591

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Alex F View Post
    This thread is too long, and has too much code, I don't think that anyone really reads this code.
    Well, I already was afraid of something like that some messages up the thread...

    But I promise it will be marked resolved soon. Soon, however, not now...

    However, the last post about DoEvents gives me some idea. There is classic deadlock situation with a worker thread. [...]
    Yes apparently this is exactly the problem I encountered. I didn't take into account that BackgroundWorker might use ordinary Windows messages (or is there something else that's handled by the message pump?) internally for its inter-thread communication.

    Use BeginInvoke instead of Invoke, and this solves the problem. [...]
    I didn't call either of the two myself, but I strongly suspect that BackgroundWorker did it internally. I'll go and read up on these methods the next days to learn more about the mechanisms of multithreading in the CLR environment.

    Also, it is a good idea to create thread manually at least once, instead of using BackgroundWorker. This allows to understand what really happens in multithreaded program. BackgroundWorker hides important implementation details.
    I'm afraid your'e pretty darn right! I already considered using Threading::Thread instead of BackgroundWorker while looking for solutions, mostly because the convenience of its Join() method. But then I would have had to rewrite significant amounts of code and lose some of the convenience offered by BackgroundWorker, and so I refrained from doing so. Maybe I would have found a solution much quicker if I hadn't...

    Thanks for the link to codeproject.com. I have seen the sample code there uses Threading::Thread, so I will study it in detail soon.

    And now another question to the entire audience, that leads back to the original title of that thread: While fighting the deadlock problem I had put aside the fact that now again setting the form's UseWaitCursor property to true near the beginning of Form1::DoCount(FileStream ^) apparently has no effect. (I do see a wait cursor, however, for a short period after closing the progress dialog and before repainting the Chart control.)

    I found a discussion about this problem here at MSDN, but the solution of calling the evil Application::DoEvents() right after setting UseWaitCursor didn't help either. The alternative of using Form::Cursor::Current instead, that I think I found elsewhere on MSDN was no solution too. A lack of message processing doesn't seem to be the problem here, as repainting works like a charm. Any tips?

    What I don't want to do is setting the wait cursor application-wide, as I don't want it for the progress dialog. But maybe that wouldn't work either...

    TIA again
    Last edited by Eri523; September 18th, 2010 at 04:05 PM.

  11. #26
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,358

    Re: How to UseWaitCursor correctly

    Before starting the worker thread, just save off the current cursor (using the Form's Cursor property) and set the new cursor.

    When you receive the thread complete (or abort method), restore the cursor by setting the form cursor to the one you saved off.

  12. #27
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,591

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Arjay View Post
    Before starting the worker thread, just save off the current cursor (using the Form's Cursor property) and set the new cursor.

    When you receive the thread complete (or abort method), restore the cursor by setting the form cursor to the one you saved off.
    I thought I'd already tried that, but to be sure I tried it again, following your advice as close as possible.

    I replaced the two assignments to the form's UseWaitCursor property in the method Form1::DoCount(FileStream ^) by

    Code:
      System::Windows::Forms::Cursor ^csrSaved=Cursor->Current;
      Cursor->Current=Cursors::WaitCursor;
    and

    Code:
      Cursor->Current=csrSaved;
    respectively.

    (The majority of you who didn't download the entire project from the attachment to post #21 may refer to the code excerpt in post #18. There have been some changes to the project in the meantime, but I don't think they're relevant here.)

    Unfortunately: No effect, regardless of whether I follow the first code snippet by a call to Application::DoEvents() or not.

    Any clues about a possible cause for that?

    TIA

  13. #28
    Arjay's Avatar
    Arjay is offline Moderator / MS MVP Power Poster
    Join Date
    Aug 2004
    Posts
    11,358

    Re: How to UseWaitCursor correctly

    Not sure where you're putting the following code

    Quote Originally Posted by Eri523 View Post
    Code:
      System::Windows::Forms::Cursor ^csrSaved=Cursor->Current;
      Cursor->Current=Cursors::WaitCursor;
    and

    Code:
      Cursor->Current=csrSaved;
    One problem you have is that csrSaved needs to be a class field, saved off in the DoCount and restored in the RunWorkerCompleted.

    Code:
    System::Void Form1::DoCount(FileStream ^fsInfile)
    {
      // save off the cursor
      csrSaved = Cursor->Current;
    
      // other code removed for clarity
    }
    
    System::Void Form1::bwDoCount_RunWorkerCompleted(System::Object^  sender, System::ComponentModel::RunWorkerCompletedEventArgs^  e)
    {
      // restore the original cursor
      Cursor->Current = csrSaved;
    
      // other code removed for clarity
    }

  14. #29
    Join Date
    Jul 2002
    Posts
    2,516

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Eri523 View Post
    I didn't take into account that BackgroundWorker might use ordinary Windows messages (or is there something else that's handled by the message pump?) internally for its inter-thread communication....
    I didn't call either of the two myself, but I strongly suspect that BackgroundWorker did it internally.
    BackgroundWorker doesn't send anything itself, only your own code. I remember that in the beginning of this question we were talking about progress dialog. How progress information is sent to the main thread?

  15. #30
    Join Date
    Jun 2010
    Location
    Germany
    Posts
    2,591

    Re: How to UseWaitCursor correctly

    Quote Originally Posted by Arjay View Post
    One problem you have is that csrSaved needs to be a class field, saved off in the DoCount and restored in the RunWorkerCompleted.
    This doesn't help either. Without wanting to doubt your expertise, I didn't put much hope into this change anyway: The problem is not restoring the original cursor, but getting the wait cursor displayed in the first place.

Page 2 of 3 FirstFirst 123 LastLast

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  


Windows Mobile Development Center


Click Here to Expand Forum to Full Width

This is a CodeGuru survey question.


Featured


HTML5 Development Center