Author Topic: Programming an "Abort Button"  (Read 5234 times)

Alhexx

  • *
  • Posts: 1894
    • View Profile
    • http://www.alhexx.com
Programming an "Abort Button"
« on: 2005-08-16 14:22:01 »
Okay, the problem is quite simple this time, but I can imagine that the solution to the problem is a bit complex...

I want to implement an Abort button in my Kaddy application (when extracting files or loading an archive for example).

But how?
I know how it is made in VB, but I have no idea how to realize it in C...

Any ideas?

 - Alhexx

dziugo

  • *
  • Posts: 1470
    • View Profile
    • A new copy of FF7 thanks to Salk. Pack (zip/rar/etc) your saved game before sending it to me.
Programming an "Abort Button"
« Reply #1 on: 2005-08-16 15:55:16 »
Simple one:

C++ :
Code: [Select]
int go_on;

int _stdcall do_something(void)
{
   go_on = 1;
   if(go_on)
      unpack_archive("something\0",&somewhere);
   Application->ProcessMessages()
   if(go_on)
      make_list(somewhere, &my_list);
   Application->ProcessMessages()
   if(go_on)
      ...
   return go_on;  
}

void __fastcall my_form::Button1Click(TObject *Sender) //start button
{
   do_something();
   ...
}

void __fastcall my_form::Button2Click(TObject *Sender) //stop button
{
   go_on = 0;
   ...
}

Using this, you have to wait for an active function to be completed, but the rest won't be executed.

Other solution is to use threads. On for the exhaustive operations which you wish to stop sometimes, one for controlling it. When it's needed you'll just suspend it/terminate it. msdn->thread

Cyberman

  • *
  • Posts: 1572
    • View Profile
Re: Programming an "Abort Button"
« Reply #2 on: 2005-08-16 16:32:39 »
Quote from: Alhexx
Okay, the problem is quite simple this time, but I can imagine that the solution to the problem is a bit complex...

I want to implement an Abort button in my Kaddy application (when extracting files or loading an archive for example).

But how?
I know how it is made in VB, but I have no idea how to realize it in C...

Any ideas?

 - Alhexx
Are you using multiple threads on Kaddy? If so you need to add a custom message to send to the thread doing the loading/extracting to stop pause or whathave you.  It has to be thread safe but you should be able to use a semaphore variable such as:
Code: [Select]

// on starting the thread
Runinng = TRUE;

//--- in thread ---
while(Running && (// whatever you need to complete you extraction
//... actual code
}
// be sure this is false
Running = FALSE;

Then your thread can finish and if Running is set to FALSE by an abort then the thread terminates neatly.

Cyb - It's harder to do in Borland C++ but heh you're not using that :D

L. Spiro

  • *
  • Posts: 797
    • View Profile
    • http://www.memoryhacking.com/index.php
Programming an "Abort Button"
« Reply #3 on: 2005-08-17 02:54:11 »
If applications need to be thread-safe, simple boolean switches won’t be enough (Cyberman’s method isn’t the type of simple boolean switch I mean when I say that; I am just saying it to be clear).

Consider this code that could be called on two separate threads:

Code: [Select]
void CoolClass::DoNeat() {
while ( m_bLock ) {} // Wait for this function to stop being called in the other thread.
m_bLock = true;

delete [] m_pArray;
m_pArray = new Cool[235];

m_bLock = false;
}


Although the case can be rare, it can happen where the lock is set twice.

Consider what would happen if Thread1 gets to “while ( m_bLock ) {}” and sees there is no lock, so it goes on.
Thread1 then does this:
Code: [Select]
MOV EAX, 1 ; Move the “bool true” into EAX before putting it into m_bLock.  Some compilers generate this type of code and you shouldn’t assume it will generate something like “MOV m_bLock, 1” where it moves “true” into “m_bLock” directly.

Then Windows halts it and lets Thread2 resume.

Thread2 goes into the function and sees m_bLock is false, skips the while, sets m_bLock to true, and calls the “delete [] m_pArray” line.
Then Thread2 is halted and other threads get to go.

Both threads managed to get into the function at the same time.

Thread1 continues, sets m_bLock to true (already set by Thread2 anyway), then deletes the array.
Throws an exception because Thread2 just deleted the array and didn’t yet have time to renew it.





If you want to ensure your program is thread-safe, you need to use critical sections.

They are quite simple to use and they are made for just this type of thing.
Let’s give CoolClass a new member:

Code: [Select]
CRITICAL_SECTION m_csCritSection;

Initialize it in the constructor:
Code: [Select]
CoolClass::CoolClass() {
InitializeCriticalSectionAndSpinCount( &m_csCritSection, 0x80000400 );
}



Destroy it in the destructor:
Code: [Select]
CoolClass::CoolClass() {
DeleteCriticalSection( &m_csCritSection );
}


And modify our function to use it:
Code: [Select]
void CoolClass::DoNeat() {
EnterCriticalSection( &m_csCritSection );

delete [] m_pArray;
m_pArray = new Cool[235];

LeaveCriticalSection( &m_csCritSection );
}




That’s it.
When you enter the “m_csCritSection” object, no other thread can enter it until you leave it.
When our first thread enters it, the other thread will come along and try to enter it also, but Windows will ensure that only one thread can be in it at a time.
The other thread is forced to wait until we call LeaveCriticalSection( &m_csCritSection ); in our first thread.



Alhexx, although you will certainly need two threads (one to maintain keyboard input/distribute Windows messages, and ensure the user can press Cancel, and another to actually perform the extraction operation), you will probably not need to go into critical sections or perform any other thread-related synchronization UNLESS you have any other threads accessing the extraction thread’s resources while it is extracting.
What I mean is:
If you have one thread for handling user input (for the cancel button) and that is all that thread does, then you do not need to use thread synchronization (I will explain why after this).
But if your input-handling thread (or any others) are reading information from the extraction thread’s resources (for example to show the user progress) then you need to create clever locks/unlocks on those resources using critical sections.




However, I don’t think you will need any critical sections at all.
Here is the plan I would suggest (similar to Cyberman’s but a few more details):
You create a second thread to handle the extraction, leaving your main thread available to continue accepting input.
You will store all your information into a structure or class while you extract, so give that structure or class a member called “m_bCancel”.
When the extraction begins it is set to false.
When you hit the button, it is set to true.
Then your extraction thread checks it whenever it is ready and if it is true, it finishes extracting and closes.
Changing the flag from false to true happens on a thread that is not the extraction thread obviously, but synchronization is not needed because the extraction thread just checks it when it wants.  the checking of the flag only happens on the extraction thread, and ensures that the extraction thread can properly finish its current task and close properly.

If you want to show the user your current progress, the extraction thread will handle this.
When the extraction thread has something new to show the user, it simply shows it and continues with the next file.
This means your outside threads are not trying to pear into your extraction thread for information that may or may not be accessible at that time.
You can give the structure/class used for extraction another member which will allow it access the update dialog directly (HWND m_hWnd for example) and then it updates on its own.



A second note, when using a second thread to extract the files, it is recommended that you not terminate it from another thread.
It should have its own termination routine which frees any resources it was using from the extraction process (which I assume would be a lot) and then calls “ExitThread( 0 )” to terminate itself.
This “CoolClass::FreeResourcesAndClose()” function would be called when the extraction thread detects that its m_bCancel member has been set to true.
But there is one last thing to do.
This thread is actually going to close your “Extracting, Please Wait…” dialog (or whatever dialog you are using that allows the user to cancel).

CoolClass::FreeResourcesAndClose() should send a message to that dialog (which as you recall is a member of our structure/class [m_hWnd]) to close it.
So inside the CoolClass::FreeResourcesAndClose() function you would have this line:
Code: [Select]
EndDialog( m_hWnd, 0 );

Again, don’t have to worry about threading issues here.
This will send a message to that dialog telling it to close, which your main thread will handle when it is ready (in your message pump, where all Windows messages go and wait to be handled).


If you don’t want it to just close, you can define your own user message:
Code: [Select]
#define WM_NOTIFYENDOFEXTACTION   WM_USER + 0x01
Then send that message to the dialog via SendMessage() and handle it in the dialog’s message handler.


L. Spiro








OTHER USEFUL HINTS:
You can save a few instructions by calling CreateRemoteThread( -1, … ) instead of CreateThread().  CreateThread() only calls CreateRemoteThread() with the hHandle parameter set to -1.  Of course you won’t be calling CreateRemote thread in a loop but it is interesting information anyway.

CreateThread()/CreateRemoteThread() allow one parameter to be passed to the new thread.
This may seem as though it is not enough in some cases, but remember you can pass the address of a structure, and that structure then has additional information you want to pass to the thread.
You can (and should) create a structure that has all the parameters you want to pass to the thread (options for example).
But be careful with how you do this.
The structure can not be created on the stack. You have to use “new” so that the second thread can access it “when it is ready”.
If the creation of the new thread fails, your first thread must “delete” this object on its own.
If the new thread is created correctly, the first thread can continue, and the new thread will then be responsible for “delete”ing the structure after it is done with it.


Code: [Select]
struct ExtractParms {
int iWidth, iHeight;
unsigned long ulDaysInyear;
};
void CreateExtractionThread( int iWidth, int iHeight, unsigned long ulTotalDaysInTheYear ) {
ExtractParms * pexpParms = new ExtractParms();
pexpParms->iWidth = iWidth;
pexpParms->iHeight = iHeight;
pexpParms->ulDaysInyear = ulTotalDaysInTheYear;

HANDLE hThread = CreateThread( NULL, 0, (LPTHREAD_START_ROUTINE)ExtractRoutine, (LPVOID)pexpParms, 0, NULL );

if ( hThread == NULL ) {
delete pexpParms;
}

}


DWORD WINAPI ExtractRoutine( ExtractParms * pexpParms ) {
if ( pexpParms->ulDaysInyear == 0 ) {
delete pexpParms;
return 0;
}
delete pexpParms;
return 1;
}

Cyberman

  • *
  • Posts: 1572
    • View Profile
Thanks L. Spiro!
« Reply #4 on: 2005-08-17 15:55:05 »
I suppose I tend to make things seem to simple or is it I make things too complicated? Hmmm

In any case your suggestion is essentially what I was suggesting (can you say redundant and repititious?).  The main difference is I always use synchronisation if updating a component owned by another thread.  It's just a good habit for me, otherwise I won't do it and it will come back to haunt me later. <-- ADD strikes back.

Anyhow for the main thread I was thinking it would be best to use a message for the button event and let the processing thread play with the semaphore itself.  This is what I do for my scanning thread in PSXview (the improved TV).  It also allows for the thread to be 'reentrant' from the main thread without causing 'ISSUES' :)

Allhexx - in this case with threads don't be lazy or it will lock up your application.  I've had it happen way too many times to not be concerned about thread synchronization.  Threads are handy but make things more complicated, so think things through before implementing (this is personal pain.. I mean experience ). It's worth the extra time and effort to get it right the first time, because it's very likely you will have to redo (like me) if you don't.

Cyb

L. Spiro

  • *
  • Posts: 797
    • View Profile
    • http://www.memoryhacking.com/index.php
Programming an "Abort Button"
« Reply #5 on: 2005-08-18 02:45:18 »
The first time I read your post from above I misread one part and went off on my own tangent.
I wrote that whole blurb.
Posted, then went back to read to make sure it followed the stream of the topic.

Then I realized I misread your post.
Of course with that comes realizing I wrote too much about nothing.
But it has information some can use for other projects (and this one too) anyway and can help some get into critical sections, so I did not want to delete the whole thing.

Just add to the knowledge base.
Whatever.


L. Spiro

Alhexx

  • *
  • Posts: 1894
    • View Profile
    • http://www.alhexx.com
Programming an "Abort Button"
« Reply #6 on: 2005-08-23 19:24:54 »
Sorry I didn't reply sooner...

Critical Sections.... beh, just wrote a test about that a few weeks ago...

As for multi-threading:
No, I never programmed multi-thread applications before, so this means I'll have a long way to go to reach my goal then.

Okay, I will keep your notes in mind, and I'll try to play around with multi-thread apps as soon as I find some time.

Thank you!

 - Alhexx

L. Spiro

  • *
  • Posts: 797
    • View Profile
    • http://www.memoryhacking.com/index.php
Programming an "Abort Button"
« Reply #7 on: 2005-08-24 03:08:39 »
If that is the case, remember to use “volatile” when needed and use compiler option /MT (Multi-Threaded).

In regards to volatile:
Sometimes compiler optimizations will cause a value to be loaded into a register and kept there if it detects that that region of code makes no changes to the variable.

Code: [Select]
while ( m_bLocked ) {}
m_bLocked = true;
…


In this case, the compiler will realize that inside the while loop, m_bLocked is not changed, so it stores it in a register for use during the entire while loop.  This of course causes a lock-up, since we expect m_bLocked to be changed by another thread, which will change the original, but it will not change the registers in our current thread.

By specifying it as volatile, it will not be subjected to these types of optimizations.  Each time it is used, it will be reloaded from its actual address, even if it was already loaded into a register and still sitting there.
This of course reduces efficiency, so you only want to use volatile when directly needed.


L. Spiro