How to stop windows from showing program as "not responding" Topic is solved

If you are using the main C++ distribution of wxWidgets, Feel free to ask any question related to wxWidgets development here. This means questions regarding to C++ and wxWidgets, not compile problems.
Post Reply
ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

How to stop windows from showing program as "not responding"

Post by ValeV » Tue Apr 16, 2019 8:07 am

I made a program that works fine, but does quite a bit of work on button press. When the work is being done, the program is shown as not responding (like when it crashes), but when program finishes the work, it is responsive again.

My question is obvious - can I stop windows from marking program as "not responding"?

Link how it looks when it hangs, in case I was not understood: https://pasteboard.co/Ian49iA.png

catalin
Moderator
Moderator
Posts: 1512
Joined: Wed Nov 12, 2008 7:23 am
Location: Romania

Re: How to stop windows from showing program as "not responding"

Post by catalin » Tue Apr 16, 2019 8:27 am

ValeV wrote:
Tue Apr 16, 2019 8:07 am
can I stop windows from marking program as "not responding"?
You definitely can. There are other programs that don't behave like that, right?
The best way to stop it is to move the time consuming work into a secondary thread. Anything other than that would be (until proven otherwise) a poor solution to the problem. The UI should always be responsive.
Of course, a poor solution is better than none. There is DisableProcessWindowsGhosting() out there.

ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

Re: How to stop windows from showing program as "not responding"

Post by ValeV » Wed Apr 17, 2019 7:06 am

Thank you for solution.

My time consuming work is done when user clicks a button. Would this solution be ok?
1) create new function doWork(), transfer all stuff from onButtonClick() into it
2) in function onButtonClick(), make new thread and give it doWork() function

Then the program would not show as not responding, right?

catalin
Moderator
Moderator
Posts: 1512
Joined: Wed Nov 12, 2008 7:23 am
Location: Romania

Re: How to stop windows from showing program as "not responding"

Post by catalin » Wed Apr 17, 2019 8:09 am

It might be slightly more complicated than that, as you probably need to sync with the worker thread when the work has ended, and/or show a progress dialog while the work is being done, possibility to cancel etc - there are some UI "good practices" for such things.
But in a few words that should work, yes (in the button event handler start a new thread that will do the work, then return the control of the main thread to the event loop).

ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

Re: How to stop windows from showing program as "not responding"

Post by ValeV » Wed Apr 17, 2019 8:35 am

Thanks, I made this changes, but program stops responding when it comes in touch with code that has anything to do with MainFrame. Let me explain.

Button event handler:

Code: Select all

void DataGetterFrame::OnButton2Click(wxCommandEvent& event)
{
    HANDLE m_hThread = (HANDLE)_beginthreadex(0, 0, &doWork, 0, 0, 0);
    WaitForSingleObject(m_hThread, INFINITE);
    CloseHandle(m_hThread);

    izpisi("Data saved to file " + string((FileDialog2->GetPath()).mb_str()) + ".\n");
}

doWork() function:

Code: Select all

unsigned int __stdcall doWork(void* data)
{
    wxProgressDialog progress("Progress", "Calculating...", 100, MainFrame);

    int a = 5;

    //vprasamo kam shranimo
    if (MainFrame->FileDialog2->ShowModal() == wxID_OK)
    {
        if (!(MainFrame->FileDialog2->GetPath().Upper().EndsWith(".BIN")))
        {

Program stops responding when it comes to line

Code: Select all

wxProgressDialog progress("Progress", "Calculating...", 100, MainFrame);
. If I comment this line, it stops responding on

Code: Select all

if (MainFrame->FileDialog2->ShowModal() == wxID_OK)
(so int a = 5 goes through).

MainFrame is global variable

Code: Select all

DataGetterFrame* MainFrame
What's the problem? Should I pass MainFrame as parameter to doWork() function instead?

catalin
Moderator
Moderator
Posts: 1512
Joined: Wed Nov 12, 2008 7:23 am
Location: Romania

Re: How to stop windows from showing program as "not responding"

Post by catalin » Wed Apr 17, 2019 8:43 am

That is because you can only do non-GUI work in a secondary thread (no matter what type of threads you use).
https://docs.wxwidgets.org/trunk/overview_thread.html

User avatar
doublemax
Moderator
Moderator
Posts: 13600
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to stop windows from showing program as "not responding"

Post by doublemax » Wed Apr 17, 2019 9:44 am

There are several things to consider.

1) Can the user do something useful while the "long running task" is busy or not?

If not, you should display a progress dialog while it's busy.
This makes everything a little easier, because the progress dialog effectively blocks the rest of the GUI. All you need to do is update it regularly.

2) Can the work be split in smaller parts?
This is true in most cases where e.g. you write or process a text file line by line or an RGB image row by row.
If yes, you can get away without using threads. Just call wxProgressDialog::Update() or Pulse() inbetween steps. This will also wake up the event loop and your app will not be marked as "not responding".

Code: Select all

wxProgressDialog dlg(...);
for( int i=0; i<10000; i++)
{
  [...] // do some work
  
  // don't call this too often as this will slow down the whole process.
  // 5-10 times per second max
  dlg.Update() or dlg.Pulse();
}
Disadvantage: You're not taking advantage of multiple CPU cores as you're only using one thread.

A case where this is not possible may be when you use an external library that is not under your control. You just call a function with some parameters and it returns after a (possibly) long time. In that case, or if you want to use multiple cores, you need to use worker threads.

In that case i would use a pattern line this:

Code: Select all

wxProgressDialog dlg(...);
int status = BUSY;
MyThread *thread = new MyThread( &status );
while( status == BUSY )
{
  dlg.Update() or dlg.Pulse();
  wxMilliSleep(100);
}
MyThread will be a "DETACHED" thread that destroys itself when it's done. Therefore i won't use something like thread->GetStatus() to check its status (because it could already be destroyed and the call would crash). Instead i pass a pointer to a variable into the thread and the thread will update this status while it's working. This could just be a BUSY/DONE flag or a progress indicator.
Use the source, Luke!

ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

Re: How to stop windows from showing program as "not responding"

Post by ValeV » Wed Apr 17, 2019 11:45 am

Thank a lot to both. I understand now better how to do this things.

What I have now is:

Code: Select all

HANDLE m_hThread = (HANDLE)_beginthreadex(0, 0, &doWork, 0, 0, 0);
        DWORD dwWaitResult = WAIT_TIMEOUT;
        while(dwWaitResult != WAIT_OBJECT_0)
        {
            for (int j = 1; j <= 90; j += 5)
            {
                dwWaitResult = WaitForSingleObject(m_hThread, 10);
                if (dwWaitResult == WAIT_OBJECT_0)
                {
                    progress.Update(100);
                    break;
                }
                progress.Update(j, "Reading data: " + to_string(j) + " %");
                Sleep(200);
            }
        }
        CloseHandle(m_hThread);
(I have to use _beginthreadex() to make a thread)

Is there more elegant way to do this? I don't want program to show as not responding, so I am always updating wxProgressBar. Since I don't know when the thread will end, I go from the start again and again (by testing, it needs 1 or 2 full for loops, but still).

User avatar
doublemax
Moderator
Moderator
Posts: 13600
Joined: Fri Apr 21, 2006 8:03 pm
Location: $FCE2

Re: How to stop windows from showing program as "not responding"

Post by doublemax » Wed Apr 17, 2019 12:09 pm

If you can't show the progress, call Pulse() instead of Update(). Then you can remove the outer for-loop.
Use the source, Luke!

ValeV
Experienced Solver
Experienced Solver
Posts: 68
Joined: Wed Jan 30, 2019 1:39 pm

Re: How to stop windows from showing program as "not responding"

Post by ValeV » Wed Apr 17, 2019 12:37 pm

Thanks! Did it like you said, it looks better now. :)

I think the thread can be closed, I have no further related questions.

catalin
Moderator
Moderator
Posts: 1512
Joined: Wed Nov 12, 2008 7:23 am
Location: Romania

Re: How to stop windows from showing program as "not responding"

Post by catalin » Wed Apr 17, 2019 12:56 pm

ValeV wrote:
Wed Apr 17, 2019 12:37 pm
I think the thread can be closed
You can set it to "Solved" by accepting one of the replies (see the 'v'-like button next to each reply).

Post Reply