How to detect when a process has completed

jbsoundjbsound Posts: 35
edited January 11, 2008 5:50AM in SQL Toolkit Previous Versions
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!

JB

Comments

  • Brian DonahueBrian Donahue Posts: 6,590 Bronze 1
    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.
  • 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.

    Thanks,

    JB
  • Brian DonahueBrian Donahue Posts: 6,590 Bronze 1
    Hi,

    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:

    0%|
    20%
    40%
    60%
    80%
    |100%
    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.
  • 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:
    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.

    JB
  • Brian DonahueBrian Donahue Posts: 6,590 Bronze 1
    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.
  • 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!

    JB
  • RawdenRawden Posts: 34 Bronze 2
    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...
  • Brian DonahueBrian Donahue Posts: 6,590 Bronze 1
    Hi,

    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?
  • RawdenRawden Posts: 34 Bronze 2
    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,

    Rawden.
  • Brian DonahueBrian Donahue Posts: 6,590 Bronze 1
    Hi,

    ...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?
  • RawdenRawden Posts: 34 Bronze 2
    Correct. Well, less user input, but more Status updates yeah.
  • Brian DonahueBrian Donahue Posts: 6,590 Bronze 1
    edited January 9, 2008 8:43AM
    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! :-)
    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
            Application.DoEvents()
            '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
            Application.DoEvents()
            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})
                Me.EndInvoke(arx)
                Return
            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
            Get
                Return _userState
            End Get
        End Property
    
        Public ReadOnly Property AsyncWaitHandle() As System.Threading.WaitHandle Implements System.IAsyncResult.AsyncWaitHandle
            Get
                Return _waitHandle
            End Get
        End Property
    
        Public ReadOnly Property CompletedSynchronously() As Boolean Implements System.IAsyncResult.CompletedSynchronously
            Get
                Return False
            End Get
        End Property
    
        Public ReadOnly Property IsCompleted() As Boolean Implements System.IAsyncResult.IsCompleted
            Get
                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)
            _waitHandle.Set()
            If Not _callback Is Nothing Then _callback(Me)
        End Sub
        Function Finish() As Database
            AsyncWaitHandle.WaitOne()
            AsyncWaitHandle.Close()
            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
    
  • RawdenRawden Posts: 34 Bronze 2
    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?
  • Brian DonahueBrian Donahue Posts: 6,590 Bronze 1
    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 DonahueBrian Donahue Posts: 6,590 Bronze 1
    This one redisters for Data Compare on two threads, and also reports the registration process' current task. Since this is all asynchronous, you would more than likely see things happening in a mixed-up order.
    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")
            db1.Status = New StatusEventHandler(AddressOf Me.Status1CallBack)
            db2.Status = New StatusEventHandler(AddressOf Me.Status2CallBack)
            TextBox1.Text = TextBox1.Text & "Registering " & db1.ConnectionProperties.DatabaseName & " on server " & db1.ConnectionProperties.ServerName & vbCrLf
            Application.DoEvents()
            '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
            Application.DoEvents()
            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})
                Me.EndInvoke(arx)
                Return
            End If
            TextBox1.AppendText(msg & Chr(13) & Chr(10))
        End Sub
    
        Public Sub Status1CallBack(ByVal o As Object, ByVal e As StatusEventArgs)
            If e.Percentage = -1 Then OutputMessage(db1.ConnectionProperties.DatabaseName & ": " & e.Message)
        End Sub
        Public Sub Status2CallBack(ByVal o As Object, ByVal e As StatusEventArgs)
            If e.Percentage = -1 Then OutputMessage(db2.ConnectionProperties.DatabaseName & ": " & e.Message)
        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
            Get
                Return _userState
            End Get
        End Property
    
        Public ReadOnly Property AsyncWaitHandle() As System.Threading.WaitHandle Implements System.IAsyncResult.AsyncWaitHandle
            Get
                Return _waitHandle
            End Get
        End Property
    
        Public ReadOnly Property CompletedSynchronously() As Boolean Implements System.IAsyncResult.CompletedSynchronously
            Get
                Return False
            End Get
        End Property
    
        Public ReadOnly Property IsCompleted() As Boolean Implements System.IAsyncResult.IsCompleted
            Get
                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)
            _waitHandle.Set()
            If Not _callback Is Nothing Then _callback(Me)
        End Sub
        Function Finish() As Database
            AsyncWaitHandle.WaitOne()
            AsyncWaitHandle.Close()
            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
    
  • RawdenRawden Posts: 34 Bronze 2
    Well, I'm officially out of my depth. But I got it working. Thanks.

    I orignally had it wrapped up in a class. Would I need to change much on your code to get it working in a class. It's moaning about no InvokeRequired method etc.
  • Brian DonahueBrian Donahue Posts: 6,590 Bronze 1
    I am not sure what you mean -- I've got quite a few classes in my example. If you wanted to make it more readable, you could remove each class to its' own code file, but it works better having all of the classes in one file so I can post the code to the forum.

    InvokeRequired should be a property of your Windows Form -- it's the thing that prevents other threads from being able to update controls on the form. It should be built-in to the Form class, so I don't know why you would get a complaint that it's not implemented.
  • RawdenRawden Posts: 34 Bronze 2
    Sorry I think I was just being an idiot. What I meant was, I had it in a re-useable class module. I put the OutputMessageDelegate, OutputMessage and the StatusCallBacks on the form and it's working now.

    I have just two more questions:

    1. What do I need to do to add in the other methods? (I am playing around with this now, so I may actually crack it with my limited knowledge anyway.)

    and

    2. If I want to give the user the ability to cancel the compare at any stage (now that you have sorted the user input side of things so they can actually press the button) would this cause problems if I were to call the CancelOperation methods?
  • RawdenRawden Posts: 34 Bronze 2
    Hi Brian,

    I'm slowly going through this asynchronous stuff and getting my head round it. :shock:

    Can you tell me if (and where) the Registration.Finish() function is called from. I can't seem to see a reference to it.

    (or rather the DatabaseServices.EndRegisterForDataCompare function actually.)

    Thanks,

    Rawden.
  • Brian DonahueBrian Donahue Posts: 6,590 Bronze 1
    Hi Rawden,

    You're right; the Finish methods never seem to run. Plus the DatabaseServices class seems to be entirely redundant. Here is an updated version that seems to be doing the job... The form has since grown a progress bar and a label; hopefully this doesn't introduce too much confusion...
    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
        Dim mappings As SchemaMappings = New SchemaMappings
        Dim currentDatabase As 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")
            currentDatabase = db1
            Application.DoEvents()
            'Start the async operation to register the database and call RegisterDatabase1Complete when complete
            ThreadPool.QueueUserWorkItem(AddressOf register, db1)
        End Sub
        Sub register(ByVal db As Object)
            Dim reg As New AsyncDatabaseMethods
            CType(db, Database).Status = New StatusEventHandler(AddressOf Me.StatusCallBack)
            UpdateProgressLabel("Register " & currentDatabase.ConnectionProperties.DatabaseName)
            If currentDatabase.ConnectionProperties.ServerName = db1.ConnectionProperties.ServerName And currentDatabase.ConnectionProperties.DatabaseName = db1.ConnectionProperties.DatabaseName Then
                reg.BeginRegisterDatabase(CType(db, Database), AddressOf RegisterDatabase1Complete, Nothing)
            Else : reg.BeginRegisterDatabase(CType(db, Database), AddressOf RegisterDatabase2Complete, Nothing)
            End If
        End Sub
        Sub script(ByVal session As Object)
            Dim scr As New AsyncDatabaseMethods
            scr.BeginGenerateScript(CType(session, ComparisonSession), True, AddressOf GenerateScriptComplete, Nothing)
        End Sub
        Sub map(ByVal dummy As Object)
            Dim comp As AsyncDatabaseMethods = New AsyncDatabaseMethods
            UpdateProgressLabel("Mapping tables")
            OutputMessage("Mapping Tables")
            mappings.Status = New StatusEventHandler(AddressOf StatusCallBack)
            comp.BeginCreateDefaultMappings(db1, db2, mappings, AddressOf TableMappingsComplete, Nothing)
    
        End Sub
        Sub compare(ByVal dummy As Object)
            Dim comp As AsyncDatabaseMethods = New AsyncDatabaseMethods
            UpdateProgressLabel("Comparing " & db1.ConnectionProperties.DatabaseName & " and " & db2.ConnectionProperties.DatabaseName)
            OutputMessage("Comparing " & db1.ConnectionProperties.DatabaseName & " and " & db2.ConnectionProperties.DatabaseName)
            Application.DoEvents()
            comp.BeginComparison(db1, db2, mappings, SessionSettings.Default, AddressOf CompareDataComplete, Nothing)
        End Sub
        'This will run when db1 is done registering
        Private Sub RegisterDatabase1Complete(ByVal r As IAsyncResult)
            UpdateProgressBar(0)
            OutputMessage("Registered " & db1.ConnectionProperties.DatabaseName & " on server " & db1.ConnectionProperties.ServerName & vbCrLf)
            currentDatabase = db2
            CType(r, AsyncDatabaseMethods).FinishRegisterDatabase()
            ThreadPool.QueueUserWorkItem(AddressOf register, db2)
        End Sub
        'This will run when db2 is done registering
        Private Sub RegisterDatabase2Complete(ByVal r As IAsyncResult)
            UpdateProgressBar(0)
            OutputMessage("Registered " & db2.ConnectionProperties.DatabaseName & " on server " & db2.ConnectionProperties.ServerName & vbCrLf)
            CType(r, AsyncDatabaseMethods).FinishRegisterDatabase()
            Dim mappings As SchemaMappings = New SchemaMappings
            ThreadPool.QueueUserWorkItem(AddressOf map, Nothing)
        End Sub
        Private Sub TableMappingsComplete(ByVal r As IAsyncResult)
            UpdateProgressBar(0)
            CType(r, AsyncDatabaseMethods).FinishCreateDefaultMappings()
            ThreadPool.QueueUserWorkItem(AddressOf compare, Nothing)
        End Sub
        Private Sub CompareDataComplete(ByVal r As IAsyncResult)
            Dim session As ComparisonSession = CType(r, AsyncDatabaseMethods).FinishComparison()
            UpdateProgressBar(0)
            OutputMessage("Generating SQL Script...")
            ThreadPool.QueueUserWorkItem(AddressOf script, session)
        End Sub
        Private Sub GenerateScriptComplete(ByVal r As IAsyncResult)
            Dim script As String = CType(r, AsyncDatabaseMethods).FinishGenerateScript()
            UpdateProgressBar(0)
            UpdateProgressLabel("Idle")
            OutputMessage(script)
        End Sub
    #Region "Form Control update delegates"
        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})
                Me.EndInvoke(arx)
                Return
            End If
            TextBox1.AppendText(msg & Chr(13) & Chr(10))
        End Sub
        Private Delegate Sub UpdateProgressLabelDelegate(ByVal msg As String)
        Private Sub UpdateProgressLabel(ByVal msg As String)
            If Me.InvokeRequired Then
                ' if operating on a thread, invoke a delegate 
                ' on the UI thread. 
                Dim upl As UpdateProgressLabelDelegate = _
                New UpdateProgressLabelDelegate(AddressOf UpdateProgressLabel)
                Dim arx As IAsyncResult = Me.BeginInvoke( _
                  upl, New Object() {msg})
                Me.EndInvoke(arx)
                Return
            End If
            Label1.Text = msg
        End Sub
        Private Delegate Sub UpdateProgressBarDelegate(ByVal percentage As Integer)
        Private Sub UpdateProgressBar(ByVal percentage As Integer)
            If Me.InvokeRequired Then
                ' if operating on a thread, invoke a delegate 
                ' on the UI thread. 
                Dim upl As UpdateProgressBarDelegate = _
                New UpdateProgressBarDelegate(AddressOf UpdateProgressBar)
                Dim arx As IAsyncResult = Me.BeginInvoke( _
                  upl, New Object() {percentage})
                Me.EndInvoke(arx)
                Return
            End If
            ProgressBar1.Increment(percentage)
            ProgressBar1.Update()
        End Sub
    #End Region
        Public Sub StatusCallBack(ByVal o As Object, ByVal e As StatusEventArgs)
            If e.Percentage = -1 Then
                If Not currentDatabase Is Nothing Then OutputMessage(currentDatabase.ConnectionProperties.DatabaseName & ": " & e.Message)
            Else : UpdateProgressBar(e.Percentage - ProgressBar1.Value)
            End If
        End Sub
    End Class
    Public Class AsyncDatabaseMethods
        Implements IAsyncResult
        Dim _userState As Object
        Dim _waitHandle As ManualResetEvent = New ManualResetEvent(False)
        Dim _database1 As Database
        Dim _database2 As Database
        Dim _database As Database
        Dim _session As ComparisonSession = New ComparisonSession
        Dim _script As String
        Dim _sessionsettings As SessionSettings
        Dim _mappings As SchemaMappings
        Dim _callback As AsyncCallback
    #Region "IAsyncResult Members"
        Public ReadOnly Property AsyncState() As Object Implements System.IAsyncResult.AsyncState
            Get
                Return _userState
            End Get
        End Property
    
        Public ReadOnly Property AsyncWaitHandle() As System.Threading.WaitHandle Implements System.IAsyncResult.AsyncWaitHandle
            Get
                Return _waitHandle
            End Get
        End Property
    
        Public ReadOnly Property CompletedSynchronously() As Boolean Implements System.IAsyncResult.CompletedSynchronously
            Get
                Return False
            End Get
        End Property
    
        Public ReadOnly Property IsCompleted() As Boolean Implements System.IAsyncResult.IsCompleted
            Get
                Return _waitHandle.WaitOne(0, False)
            End Get
        End Property
    #End Region
        Sub BeginComparison(ByVal db1 As Database, ByVal db2 As Database, ByVal mappings As SchemaMappings, ByVal settings As SessionSettings, ByVal callback As AsyncCallback, ByVal state As Object)
            '_database1 = db1
            '_database2 = db2
            _callback = callback
            _userState = state
            _mappings = mappings
            _session.CompareDatabases(db1, db2, mappings, settings)
            _waitHandle.Set()
            If Not _callback Is Nothing Then _callback(Me)
        End Sub
        Function FinishComparison() As ComparisonSession
            AsyncWaitHandle.WaitOne()
            AsyncWaitHandle.Close()
            FinishComparison = _session
        End Function
        Sub BeginCreateDefaultMappings(ByVal db1 As Database, ByVal db2 As Database, ByVal mappings As SchemaMappings, ByVal callback As AsyncCallback, ByVal state As Object)
            _callback = callback
            _userState = state
            _mappings = mappings
            _mappings.CreateMappings(db1, db2)
            _waitHandle.Set()
            If Not _callback Is Nothing Then _callback(Me)
        End Sub
        Function FinishCreateDefaultMappings() As SchemaMappings
            AsyncWaitHandle.WaitOne()
            AsyncWaitHandle.Close()
            FinishCreateDefaultMappings = _mappings
        End Function
        Sub BeginRegisterDatabase(ByVal db As Database, ByVal callback As AsyncCallback, ByVal state As Object)
            _database = db
            _callback = callback
            _userState = state
            _database.RegisterForDataCompare(_database.ConnectionProperties, Options.Default)
            _waitHandle.Set()
            If Not _callback Is Nothing Then _callback(Me)
        End Sub
        Function FinishRegisterDatabase() As Database
            AsyncWaitHandle.WaitOne()
            AsyncWaitHandle.Close()
            FinishRegisterDatabase = _database
        End Function
        Sub BeginGenerateScript(ByVal session As ComparisonSession, ByVal runontwo As Boolean, ByVal callback As AsyncCallback, ByVal state As Object)
            Dim provider As New SqlProvider
            _callback = callback
            _userState = state
            Dim block As ExecutionBlock = provider.GetMigrationSQL(session, runontwo)
            _script = block.GetString()
            _waitHandle.Set()
            If Not _callback Is Nothing Then _callback(Me)
        End Sub
        Function FinishGenerateScript() As String
            AsyncWaitHandle.WaitOne()
            AsyncWaitHandle.Close()
            FinishGenerateScript = _script
        End Function
    End Class
    
  • RawdenRawden Posts: 34 Bronze 2
    Brilliant! Thanks a lot Brian. It works really well.

    I've changed the databases and session objects etc. to go in ByRef and that way when I cancel the operation on the form, they cancel in the Async methods as well. I can then also do away with the AsyncDatabaseMethod variables and the Finish functions.

    :lol:
Sign In or Register to comment.