Question What happens to a killed process? Does it exit properly?

Sprint

Well-known member
Joined
Feb 3, 2006
Messages
58
Location
Ohio
Programming Experience
5-10
I have a app that is starting up a thread that runs the standard windows defrag utility. It also gives the user the option to cancel the defrag if they want to. Problem is if the thread defrag is running on is canceled is there any data loss or does it just do the last operation and then exit. For example the code goes something like this:

Code:
    Dim myDefragThread As New System.Threading.Thread(AddressOf DefragmentHardDrive)
    Dim CancelDefrag As Boolean = False

 Private Sub DefragmentHardDriveBarButtonItem_ItemClick(ByVal sender As Object, ByVal e As DevExpress.XtraBars.ItemClickEventArgs) Handles DefragmentHardDriveBarButtonItem.ItemClick
        If DefragmentHardDriveBarButtonItem.Caption = "Defragment My Hard Drive" Then
            If DisplayDialogBoxForm("This will start a disk defragmentation which can take anywhere from 5 minutes to a couple hours.  " _
            "Your machine may run sluggish during the process but can still be used as normal.  You can cancel the defrag if needed by " _ 
            "selecting the Cancel Defragment menu item.  Do you want to continue?", "Defragment Disk?", True, , True) = Windows.Forms.DialogResult.Yes Then
                CancelDefrag = False
                DefragmentHardDriveBarButtonItem.Caption = "Cancel Hard Drive Defragment"
                myDefragThread.Start()
            End If
        Else
            ' Cancel was clicked.  Check if thread is running and if so cancel it.
            If myDefragThread.IsAlive Then
                myDefragThread.Abort()
                myDefragThread.Join()
                MsgBox("Defrag Canceled")
            End If
            DefragmentHardDriveBarButtonItem.Caption = "Defragment My Hard Drive"
           CancelDefrag = True
        End If
    End Sub


    Private Sub DefragmentHardDrive(ByVal stateInfo As Object)
        Try
            Dim myProcess As New Process
            myProcess.StartInfo.FileName = "C:\WINDOWS\system32\cmd.exe"
            myProcess.StartInfo.Arguments = "/c defrag c:"
            myProcess.StartInfo.ErrorDialog = True
            myProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
            myProcess.Start()
            myProcess.WaitForExit()
           MsgBox("Defrag Complete!")
        Catch ex As Exception
            ' Only want to show a error if there was a problem and defrag wasn't canceled.
            If CancelDefrag = False Then DisplayDialogBoxForm("There was a error during the disk defragmentation.", "Error During Task", False, , True)
        End Try
Code works exactly as it should, runs the defrag on a separate thread so my UI remains responsive. I'm just worried about the effect of the cancel on it, when you call a thread abort does the program running in the thread know it's being canceled and "cleanly" close? I don't want to abort a file move operation when it's defraging and end up with a corrupt file.
 
Last edited:

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,412
Location
Norway
Programming Experience
10+
Process and thread is two different things, abort/kill should be avoided in both cases as far as possible. In your case, aborting the thread will only interrupt the WaitForExit blocking call, and not affect the independent cmd/defrag process.
Code works exactly as it should, runs the defrag on a separate thread so my UI remains responsive.
You don't need to use threads here, the Process.Exited Event can be used instead. To cancel the cmd/defrag process requires some special measures, to make a long story short just copy this code, call CtrlEvent supplying the process id and event type to send a ctrl-break to the console process:
Code:
Public Enum ConsoleCtrlEvent
    CTRL_C = 0
    CTRL_BREAK = 1
    CTRL_CLOSE = 2
    CTRL_LOGOFF = 5
    CTRL_SHUTDOWN = 6
End Enum

<STAThread()> _
Private Sub CtrlEvent(ByVal processId As Integer, ByVal evt As ConsoleCtrlEvent)
    FreeConsole()
    AttachConsole(processId)
    GenerateConsoleCtrlEvent(evt, 0)
End Sub

Private Declare Function GenerateConsoleCtrlEvent Lib "kernel32.dll" (ByVal sigevent As ConsoleCtrlEvent, ByVal dwProcessGroupId As Integer) As Boolean
Private Declare Function FreeConsole Lib "kernel32.dll" () As Boolean
Private Declare Function AttachConsole Lib "kernel32.dll" (ByVal dwProcessId As Integer) As Boolean
usage sample:
Code:
CtrlEvent(myProcess.Id, ConsoleCtrlEvent.CTRL_BREAK)
 

Sprint

Well-known member
Joined
Feb 3, 2006
Messages
58
Location
Ohio
Programming Experience
5-10
How would I keep my UI responsive and able to do other things if I don't use a separate thread for it?
 
Last edited:

Sprint

Well-known member
Joined
Feb 3, 2006
Messages
58
Location
Ohio
Programming Experience
5-10
Oh so your saying because I'm running the command through the OS command processor I'm essentially handing it off? But I tried doing it without a separate thread and just using the process directly, my program hung waiting for the process to exit which is not what I wanted. As coded it does what I want, I just want to "cleanly" cancel the defrag process.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,412
Location
Norway
Programming Experience
10+
Sprint said:
my program hung waiting for the process to exit which is not what I wanted.
JohnH said:
You don't need to use threads here, the Process.Exited Event can be used instead.
In case you didn't understand, or perhaps even read it (?), what this means is that you don't need to call WaitForExit, you can just listen for the Exited event.
 

Sprint

Well-known member
Joined
Feb 3, 2006
Messages
58
Location
Ohio
Programming Experience
5-10
Yeah I wasn't understanding what you were trying to say. So your saying if I remove the .WaitForExit then my sub procedure will continue and return back to my UI and the process, because of the way I'm running it, will continue on it's own and not hold me up.

Now....how do I "listen" for the exit so I can update my UI that it's done or know that it's still running? Do I need a timer that is checking it from time to time, like every second look for a .Alive? Maybe that's what I don't understand which lead me to doing it the way I did.
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,412
Location
Norway
Programming Experience
10+
You can write a WithEvents variable and use the Handles keyword with the event handler method, or use the AddHandler statement to dynamically add event handler at runtime. Here you most likely only want a single defrag process running at any given time, so you only need to keep an eye on a single Process instance, so declare the WithEvents variable and select its Exited event like you do with any other control event.
 

Sprint

Well-known member
Joined
Feb 3, 2006
Messages
58
Location
Ohio
Programming Experience
5-10
JohnH

Did as you described, removed the thread and just used the process without the waitforexit. Problem is when I go to break the process the entire program exits. I'm sending it the process.id of my process but it still kills the entire app. Any other ideas or should I put it back onto it's own thread?
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,412
Location
Norway
Programming Experience
10+
hmm... weird I didn't notice that when I tested the code, I only tested it once, though :eek: That complicates just a little bit, I haven't found a way to send the ConsoleCtrlEvent now without also closing the app that sends it (or preventing close), but a quickfix is to create a separate app that has this single purpose of closing the console. This can for example be a console app named "ConsoleEventSender" that look like this:
Code:
Module Module1

    Sub Main(ByVal args() As String)
        Dim pid As Integer
        If Integer.TryParse(args(0), pid) Then
            CtrlEvent(pid, ConsoleCtrlEvent.CTRL_C)
        End If
    End Sub

' + the CtrlEvent codes posted

End Module
When you want to send the control code to the defrag console you simply start ConsoleEventSender as a process passing on the known defrag Pid:
Code:
Dim path As String = IO.Path.Combine(Application.StartupPath, "ConsoleEventSender.exe") 'if in application path
Process.Start(path, myProcess.Id.ToString)
 

JohnH

VB.NET Forum Moderator
Staff member
Joined
Dec 17, 2005
Messages
15,412
Location
Norway
Programming Experience
10+
I just tested this alternative also, it worked for me, but try for yourself:
Code:
AppActivate(myProcess.Id)
'Thread.Sleep(10) 'add if the window doesn't activate fast enough.
SendKeys.Send("^c")
The code activates the defrag console and send the Ctrl+C key combination. Sleep the UI thread a few milliseconds between the calls if it sends keys too fast before the console window activates.

I have tested both these two last solutions, and neither causes the main application to close now :)
 

Sprint

Well-known member
Joined
Feb 3, 2006
Messages
58
Location
Ohio
Programming Experience
5-10
John,

This seems to be a decent work around. I modified it slightly, little less portable but it works for me:

In my ConsoleEventSender project:
Code:
Module MainModule
    ' Sends a control code to a process.
    ' Accepts two command line arguements: ProcessID to send command to and Integer of the type of command.

    <STAThread()> Private Sub CtrlEvent(ByVal processId As Integer, ByVal evt As Integer)
        FreeConsole()
        AttachConsole(processId)
        GenerateConsoleCtrlEvent(evt, 0)
    End Sub

    Private Declare Function GenerateConsoleCtrlEvent Lib "kernel32.dll" (ByVal sigevent As Integer, ByVal dwProcessGroupId As Integer) As Boolean
    Private Declare Function FreeConsole Lib "kernel32.dll" () As Boolean
    Private Declare Function AttachConsole Lib "kernel32.dll" (ByVal dwProcessId As Integer) As Boolean

    Sub Main()
        ' Possible values for myControlEvent: CTRL_C = 0, CTRL_BREAK = 1, CTRL_CLOSE = 2, CTRL_LOGOFF = 5, CTRL_SHUTDOWN = 6
        Dim pid As Integer
        If Integer.TryParse(My.Application.CommandLineArgs(0), pid) Then
            Dim myControlEvent As Integer = CInt(My.Application.CommandLineArgs(1))
            If (myControlEvent < 0) Or (myControlEvent > 6) Or (myControlEvent = 3) Or (myControlEvent = 4) Then myControlEvent = 0
            CtrlEvent(pid, myControlEvent)
        End If
    End Sub

End Module
In my MainRibbonForm sub:
Code:
 Private Sub DefragHardDrive(ByVal EnableDisable As Boolean)
        If EnableDisable = True Then
            myDefragProcess.EnableRaisingEvents = True
            myDefragProcess.StartInfo.FileName = Environment.GetEnvironmentVariable("ComSpec")
            myDefragProcess.StartInfo.Arguments = "/c defrag " & My.Computer.FileSystem.Drives.Item(0).Name
            myDefragProcess.StartInfo.ErrorDialog = True
            myDefragProcess.StartInfo.WindowStyle = ProcessWindowStyle.Hidden
            StatusTextOnStatusBarBarStaticItem.Caption = "Status: Defragmenting Hard Drive In Progress (" & My.Computer.FileSystem.Drives.Item(0).Name & ")"
            myDefragProcess.Start()
            DefragmentHardDriveBarButtonItem.Caption = "Defragment in Progress (Cancel)"
        Else
            If myDefragProcess.HasExited = False Then
                Dim path As String = IO.Path.Combine(Application.StartupPath, "ConsoleEventSender.exe")
                Process.Start(path, myDefragProcess.Id.ToString & " 0")
                StatusTextOnStatusBarBarStaticItem.Caption = "Last Operation: Hard Drive Defragmentation Cancelled"
                End If
            End If
            DefragmentHardDriveBarButtonItem.Caption = "Defragment My Hard Drive"
        End If
    End Sub
Thanks!

-Allan
 
Top Bottom