How to detect when a process has completed

Forum for users of SQL Toolkit 3,4,and 5

How to detect when a process has completed

Postby jbsound » Fri Jul 21, 2006 10:06 pm

This is a general question:

Since we can start a process such as CompareDatabases and assign a status handler to monitor the progress, how do I detect when a certain process has finished.

I want to explicitly break the different process down and show the status of each to the user. The way I do this is by calling sub-routines (this is VS.NET 2005 using VB.NET) which all have status handlers.

However, what I am finding is that individual threads are started that may not necessarily be done when control is handed back to the UI so it can react. Thus the question of how to detect when an individual process has finished.

Hope this makes sense!

Posts: 27
Joined: Thu Dec 16, 2004 4:46 pm

Postby Brian Donahue » Mon Jul 24, 2006 9:55 am

Hi JB,

From the example code, it's when the StatusEventHandler's percentage is -1. That's when you can check the message property to see what stage the database comparison is at.
Brian Donahue
Posts: 6590
Joined: Mon Aug 23, 2004 9:48 am

Postby jbsound » Mon Jul 24, 2006 1:19 pm

Thanks, Brian.

I did notice the -1 status percentage code sample and have also used it in my application to update the progress bar. However, the problem is that the percentag = -1 cannot be reliably used to determine when the overall process has finished.

-1 is thrown in multiple times during the process (for the Compare) to indicate the completion of the individual sub-processes.

I am looking for a way to determine when the overall Compare process has finished.


Posts: 27
Joined: Thu Dec 16, 2004 4:46 pm

Postby Brian Donahue » Tue Jul 25, 2006 3:10 pm


The percentage from the status event handler returns a percentage complete for wehatever method you hook it to. For example, you put a ststuseventhandler on a database object and do a database.register, the percentage will be the percentage of registration complete. If you hook it to the CompareDatabases method, you get the percentage of comapredatabases complete.

If you want to show the percentage of completion for the whole comparison and synchronization, you'd need to divide your progress bar into chunks and use the percentage for each statuseventhandler to figure out how far along you are.

To compare and synchronize a pair of databases, you could have five distinct tasks, all returning their own percentage complete:

1. Register Database1 0-20%
2. Register Database2 21-40%
3. Compare Database1 to Database2 41-60%
4. Generate a script to synchronize database1 to database2 61-80%
5. Run the synchronization of database1 to database2 81-100%

So you'd need to set up a progress report like:

Reg DB1 |Reg DB2|Compare | Script | Sync

Then use the statuseventhandler's percentage to figure out how much of each stage has been completed. For instance if you're registering DB1, and that's 50% done, you're 50/100*20/100 percent done (10%).

This is probably not the most accurate, but the best way I can think of.
Brian Donahue
Posts: 6590
Joined: Mon Aug 23, 2004 9:48 am

Postby jbsound » Tue Jul 25, 2006 5:08 pm

Thanks, Brian. That's a very good example of using a single progress bar to show the processes as a continuation.

I understand your point. What I was talking about though is the fact that for a single process such as db.register, the e.Percentage is showing -1 when a new e.message shows up.

Here is an example of what I mean:

I did a debug.writeline(e.message + " : " + e.percentage) to show each value during the progress update. This is what it shows for the db.register call:

Code: Select all
Connecting to server : 0
Reading full text information : -1
Reading object names : -1
Reading users : -1
Reading tables : -1
 : 0
 : 2
Reading functions : -1
Reading object text : -1
 : 3
 : 4
 : 5
 : 6
 : 7
 : 8
 : 9
 : 10
 : 11
 : 12
Reading defaults : -1
Reading rules : -1
Reading user defined types : -1
Reading columns : -1
 : 13
 : 14
 : 15
 : 16
 : 17
 : 18
 : 19
 : 20
 : 21
 : 22
 : 23
 : 24
 : 25
 : 26
 : 27
 : 28
Reading views : -1
Reading stored procedures : -1
 : 29
 : 30
 : 31
 : 32
 : 33
 : 34
Reading indexes : -1
 : 35
 : 37
 : 38
Reading foreign keys : -1

There is more, but you get the picture. If I would use the e.Percentage = -1 value as a means to determine when the db.Register process has finished, it would occur every time a new message shows up.

Hope this makes sense.

Posts: 27
Joined: Thu Dec 16, 2004 4:46 pm

Postby Brian Donahue » Wed Jul 26, 2006 4:08 pm

Hi JB,

Maybe I was being a bit vague. The Database.Register method will go 0-100% to show how far the registration has got. There are also sub-tasks inside db.Register as you point out. Whenever a new subtask such as 'reading comments' kicks off, e.Percentage goes to -1 and there is (almost always) an e.Message to pick up about which sub-task is running. If you don't care that the sub-tasks are changing, just don't update your progress bar when e.Percentage is -1.

The -1 value is just there to notify you that you've entered a new sub-part of whatever method is running.
Brian Donahue
Posts: 6590
Joined: Mon Aug 23, 2004 9:48 am

Postby jbsound » Wed Jul 26, 2006 4:17 pm

Thanks, Brian.

I understood your comments and you were not being vague. Remember, the topic was "How to detect when a process has completed". It is clear to me now that I can't really use e.Percentage to determine when a process has finished.

I did get some other information about some of the other processes that get started, which do not always go all the way up to 100%.

In a couple of other topics, we discussed threads and keeping a watch on when a process finishes through that methodology.

I started this topic before I discovered that reliably I needed to work with individual threads and monitor them for completion in order to determine when a process has finished.

The online help documentation is a bit vague about all this and my suggestion would be to create a walkthrough with a threaded approach.

Thanks again for your time and help!

Posts: 27
Joined: Thu Dec 16, 2004 4:46 pm

Postby Rawden » Tue Jan 08, 2008 5:11 pm

Brian, do you have any examples on how I could achieve what jbSound was trying to: i.e. detect the end of a main process without invoking a new thread to monitor the progress?

I'm not really sure how you would monitor them for completion and I just need a nudge in the right direction if possible.

Thanks in advance...
Posts: 27
Joined: Tue Nov 07, 2006 1:07 pm

Postby Brian Donahue » Tue Jan 08, 2008 6:27 pm


I'm sorry but I don't completely understand what you're trying to achieve.

What is considered a main process, and why would you need to use multiple threads to monitor the progress?
Brian Donahue
Posts: 6590
Joined: Mon Aug 23, 2004 9:48 am

Postby Rawden » Wed Jan 09, 2008 9:39 am

Hi Brian,

When I run the RegisterForDataCompare method on a database for example, this I would consider the main process. As mentioned above, there are sub-processes to this i.e. Reading Users and Reading Tables, which have to complete before the whole Registration method completes.

Basically, I have a sub that I call before and after the RegisterForDataCompare which writes an entry to a RichTextBox on a form informing the user what it's about to do and whether it succeeded or not. I want to also show the messages from the StatusEventHandler so the user gets a lot of information back.

The problem I have is that the application does not refresh so instead of the user actually getting live status updates; they just get nothing until it's finished... and then the lot. So I thought, right, I'll put a My.Application.DoEvents in StatusEventHandler so the app can catch up, but the problem with that is, that the lines in the caller sub fire before the messages from the StatusEventHandler.

It's a bit hard to explain. It's actually something I tried to do in a project back in 2006, but have now had to revisit. I don't know if you keep all your old emails, but we had a conversation about it via email. You sent me the first email on Wed 8th November 2006 telling me to try and Invoke my StatusEventHandler from a dummy sub in my Form. I don't know too much about it, but I thought this meant is was then running on another thread. No?

If you don't have the email still I can resend it to refresh your memory if you like and then perhaps we can post something up here for the benefit of anyone else trying to do it.

In conclusion, I would just like an easy way to find out when the RegisterForDataCompare method has finished, that way I can do a loop and wait for it until it has and still get all the messages from the StatusEventHandler beforehand.

Thanks again,

Posts: 27
Joined: Tue Nov 07, 2006 1:07 pm

Postby Brian Donahue » Wed Jan 09, 2008 10:20 am


...and presumably you want to RegisterForDataCompare in a thread other than the main window's thread... otherwise you could just call RegisterForDataCompare synchronously in the main form's class. But I could see why you wouldn't want to do this as it would block user input on the main form, correct?
Brian Donahue
Posts: 6590
Joined: Mon Aug 23, 2004 9:48 am

Postby Rawden » Wed Jan 09, 2008 10:35 am

Correct. Well, less user input, but more Status updates yeah.
Posts: 27
Joined: Tue Nov 07, 2006 1:07 pm

Postby Brian Donahue » Wed Jan 09, 2008 12:24 pm

Hi Rawden,

In order to see when a method (such as RegisterForDataCompare) has completed, I think your best bet would be a custom asynchronous method. This will kick off a method asynchronously, then run the method of your choice in the calling thread when the async method is complete. If I remember right, you program in the VB language so I made a quick and dirty example (you will probably need to plumb in some proper exception handling for when something goes wrong...). Imagine you have already created a Winforms project with a button (button1) and a TextBox (TextBox1) so that when the button is clicked, a Data Compare is done between WidgetDev and WidgetLive databases. For brevity, all that is done in the example is redister for data compare. You can enhance the DatabaseServices to support more Toolkit methods and add more classes that implement IAsyncResult to handle these methods.

Hopefully you didn't think this would be easy! :-)
Code: Select all
Imports RedGate.SQLCompare.Engine
Imports RedGate.SQL.Shared
Imports RedGate.SQLDataCompare.Engine
Imports System.Threading

Public Class Form1
    Dim db1 As New Database
    Dim db2 As New Database
    Private Sub Button1_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles Button1.Click
        db1.ConnectionProperties = New ConnectionProperties(\"localhost\", \"WidgetLive\")
        db2.ConnectionProperties = New ConnectionProperties(\"localhost\", \"WidgetDev\")
        'ThreadPool.SetMinThreads(2, 2)

        TextBox1.Text = TextBox1.Text & \"Registering \" & db1.ConnectionProperties.DatabaseName & \" on server \" & db1.ConnectionProperties.ServerName & vbCrLf
        'Start the async operation to register the database and call RegisterDatabase1Complete when complete
        ThreadPool.QueueUserWorkItem(AddressOf register1, db1)
        TextBox1.Text = TextBox1.Text & \"Registering \" & db2.ConnectionProperties.DatabaseName & \" on server \" & db2.ConnectionProperties.ServerName & vbCrLf
        ThreadPool.QueueUserWorkItem(AddressOf register2, db2)
        'Start the async operation to register the database and call RegisterDatabase2Complete when complete
    End Sub
    Sub register1(ByVal db As Object)
        Dim serv As DatabaseServices = New DatabaseServices()
        serv.BeginRegisterForDataCompare(CType(db, Database), AddressOf RegisterDatabase1Complete, Nothing)
    End Sub
    Sub register2(ByVal db As Object)
        Dim serv As DatabaseServices = New DatabaseServices()
        serv.BeginRegisterForDataCompare(CType(db, Database), AddressOf RegisterDatabase2Complete, Nothing)
    End Sub
    'This will run when db1 is done registering
    Private Sub RegisterDatabase1Complete(ByVal r As IAsyncResult)
        OutputMessage(\"Registered \" & db1.ConnectionProperties.DatabaseName & \" on server \" & db1.ConnectionProperties.ServerName & vbCrLf)
    End Sub
    'This will run when db2 is done registering
    Private Sub RegisterDatabase2Complete(ByVal r As IAsyncResult)
        OutputMessage(\"Registered \" & db2.ConnectionProperties.DatabaseName & \" on server \" & db2.ConnectionProperties.ServerName & vbCrLf)
    End Sub
    Private Delegate Sub OutputMessageDelegate(ByVal msg As String)
    Private Sub OutputMessage(ByVal msg As String)
        If Me.InvokeRequired Then
            ' if operating on a thread, invoke a delegate
            ' on the UI thread.
            Dim omd As OutputMessageDelegate = _
            New OutputMessageDelegate(AddressOf OutputMessage)
            Dim arx As IAsyncResult = Me.BeginInvoke( _
              omd, New Object() {msg})
        End If
        TextBox1.AppendText(msg & Chr(13) & Chr(10))
    End Sub
End Class
' Class for running the register methods asynchronously
Public Class Registration
    Implements IAsyncResult
    Dim _userState As Object
    Dim _waitHandle As ManualResetEvent = New ManualResetEvent(False)
    Dim _database As Database
    Dim _callback As AsyncCallback
#Region \"IAsyncResult Members\"
    Public ReadOnly Property AsyncState() As Object Implements System.IAsyncResult.AsyncState
            Return _userState
        End Get
    End Property

    Public ReadOnly Property AsyncWaitHandle() As System.Threading.WaitHandle Implements System.IAsyncResult.AsyncWaitHandle
            Return _waitHandle
        End Get
    End Property

    Public ReadOnly Property CompletedSynchronously() As Boolean Implements System.IAsyncResult.CompletedSynchronously
            Return False
        End Get
    End Property

    Public ReadOnly Property IsCompleted() As Boolean Implements System.IAsyncResult.IsCompleted
            Return _waitHandle.WaitOne(0, False)
        End Get
    End Property
#End Region
    Sub Begin(ByVal db As Database, ByVal callback As AsyncCallback, ByVal state As Object)
        _database = db
        _callback = callback
        _userState = state
        _database.RegisterForDataCompare(_database.ConnectionProperties, Options.Default)
        If Not _callback Is Nothing Then _callback(Me)
    End Sub
    Function Finish() As Database
        Finish = _database
    End Function

End Class
'Async methods class -- this is called from your main thread
Public Class DatabaseServices
    Public Function BeginRegisterForDataCompare(ByVal d As Database, ByVal callback As AsyncCallback, ByVal userState As Object) As IAsyncResult
        Dim reg As Registration = New Registration
        reg.Begin(d, callback, userState)
        BeginRegisterForDataCompare = reg
    End Function
    Public Function EndRegisterForDataCompare(ByVal r As IAsyncResult) As Database
        EndRegisterForDataCompare = CType(r, Registration).Finish()
    End Function
End Class
Last edited by Brian Donahue on Wed Jan 09, 2008 1:43 pm, edited 1 time in total.
Brian Donahue
Posts: 6590
Joined: Mon Aug 23, 2004 9:48 am

Postby Rawden » Wed Jan 09, 2008 1:27 pm

Well I have to say I'm impressed with the speed of reply.... and indeed your memory!

Would I still leave the StatusEventHandler going to the form, or can I take it back to the same class now?

I tried it as it was (with the dummy sub) and the items were still in the wrong order and I also tried it just as dbSource.Status = New StatusEventHandler(AddressOf Me.StatusCallback) and that didn't seem to populate the textbox with any of the 'Minor' processes (i.e. Reading Users). I set a breakpoint in the StatusCallback and they were being passed, but some reason they were not updated on the textbox.

Did you get a StatusCallback working fine on your sample app?
Posts: 27
Joined: Tue Nov 07, 2006 1:07 pm

Postby Brian Donahue » Wed Jan 09, 2008 1:47 pm

Sorry I just had to make a change to the earlier sample as it really wasn't working asynchronously. This one does -- then I got a threading issue because TextBox1 can't be updated from a thread other than the main UI thread, so had to create ANOTHER delegate just to do that job. Messy, messy, messy.

I'll see if I can plumb in the StatusEventHandler and come up with another example.
Brian Donahue
Posts: 6590
Joined: Mon Aug 23, 2004 9:48 am


Return to SQL Toolkit Previous Versions

Who is online

Users browsing this forum: No registered users and 1 guest