Tek-Tips is the largest IT community on the Internet today!

Members share and learn making Tek-Tips Forums the best source of peer-reviewed technical information on the Internet!

  • Congratulations Chriss Miller on being selected by the Tek-Tips community for having the most helpful posts in the forums last week. Way to Go!

Process priority or pausing event loops 4

Status
Not open for further replies.

duffman82

Programmer
Joined
Jun 27, 2005
Messages
8
Location
US
i have a code that takes code from a textbox and line by line adds each line to a listbox, the problem i run into is that if your running the command it freezes the whole program until it is complete. i want to create a pause button on the form but the program thread priority is so high that all other events on the form are obsolete. so i cannot click the pause button.

i am used to visual basic 6 where i can put a simple
pause(.05) in the loop function to prevent the process from simply locking out other user events on the form.

is there a way to do this in vb.net?
 
well i see now i might need to use threading. ill look into that more.
 
does anyone have a couple examples of threading.
preferably forms rather then pasted text...

im usin visual basic express 2005

 
check out Ricks faq - faq796-5929

Sweep
...if it works dont f*** with it
...if its f****ed blame someone else
...if its your fault that its f***ed, say and admit nothing.
 
That FAQ should do what you need, but if you want more control it'll get a little more in depth. I haven't had a chance to write up more of that threading faq.

-Rick

VB.Net Forum forum796 forum855 ASP.NET Forum
[monkey]I believe in killer coding ninja monkeys.[monkey]
 
well one problem i have. it seems like threading in visual basic express 2005 is a bit different.

trying to post data to an object from a thread process is different.

listbox1.items.add ADDITEM

control accessed from a thread other then the thread it was created on

seems you have to post data to the UI in some other fashion...... any ideas?
 
cool thanks. im just makin some tools for myself so its nothing to hurry about.

i had the project done already but want to experiment more with the threading part of it. thanks again
 
Okay, this is all from frame work 1.0 (VS.Net 2k2), It should work in v1.1 (2k3) but I can't garuntee anything in v2.0 (2k5).

This code is a sample of updating a status bar's text on a for from a process running in another thread. In v1.0 I do not beleive this is required (from what I've seen) but it is "safe".

in the form with the status bar (sbrMessages):
Code:
'The private delegate for the _SetStatusBarText sub
Private Delegate Sub delSetStatusBarText(ByVal value As String)

'The private sub that acutally updates the status bar
Private Sub pSetStatusBarText(ByVal value As String)
  sbrMessages.Panels(0).Text = value
End Sub

'the public method that is called to update the status bar
Public Sub SetStatusBarText(ByVal value As String)
  If Me.InvokeRequired Then
    Dim del As New delSetStatusBarText(AddressOf _SetStatusBarText)
    Dim sArgs() As String = {value}
    Me.BeginInvoke(del, sArgs)
  Else
    _SetStatusBarText(value)
  End If
End Sub

At this point, I would love to have Chiph and RG double check that and make sure it is correct.

If that checks out though, then you just have to call the SetStatusBarText(value) method from your thread.

-Rick

VB.Net Forum forum796 forum855 ASP.NET Forum
[monkey]I believe in killer coding ninja monkeys.[monkey]
 
Rick, I needed to produce a progress report/bar for a number of DTS packages. Whilst I was able to trap the DTS events, it effectively locked the UI, so I looked into threads. After a dozen or more crashes, I settled on the method recommended in the VB.Net programmers cookbook - create a thread wrapper class (yes, Chrissie, I created a class!!) to run the worker thread and pick up the input parameters, create the thread class (which is called by the wrapper class) and create a UI updating class which runs on the worker's thread. When its necessary to update the UI, the worker creates an instance of the UI update class and passes responsibilty on to that. The UI update class uses SyncLock to call the Invoke method of the appropriate UI control and passes the address of the method that will handle actually modifying the control. The update code ends with ControlName.Refresh (which avoids using DoEvents). I'm not sure I fully understand the reasoning behind all the processes, but since Fancesco Balena (Programming VB.Net) also recommends a similar approach and it works - both the program and the computer in general remain fully responsive - I'm sticking to it, and from time to time attempting to refine it with a view to making more sense of it.
 
Rick, this is the current draft of the code - it still needs some attention ie a WITH and an EXIT FOR wouldn't go amiss - but it is being used and is stable (ot at least seems to be).

It's called with:

OnProgress:
Dim ctrledit As New CMMonthEndProgressUpdater(FForm.pbProgress, FForm.lvTaskList)
Dim s As String
s = ProgressDescription 'Add more fields later
ctrledit.ChangeTaskGrid(EventSource, s)
ctrledit = Nothing

OnStart:
Dim ctrledit As New CMMonthEndProgressUpdater(FForm.pbProgress, FForm.lvTaskList)
ctrledit.ChangeTaskGrid(EventSource, "Started", Now())
ctrledit = Nothing

and OnFinish:
Dim ctrledit As New CMMonthEndProgressUpdater(FForm.pbProgress, FForm.lvTaskList)
ctrledit.ChangeTaskGrid(EventSource, "Finished", , Now())
ctrledit.ChangeProgress()
ctrledit = Nothing


(by the way - EventSource and ProgressDescription come from the DTS event declarations).

Code:
  Public Class CMMonthEndProgressUpdater

    Private FTask As String
    Private FStatus As String
    Private FStart As Date
    Private FFinish As Date
    'Private FDuration As double
    Private FPosition As Integer

    Private FProgress As RCC.rccProgressBar
    Private FTaskGrid As GlacialComponents.Controls.GlacialList

    Public Sub New(ByVal AProgress As RCC.rccProgressBar, ByVal ATaskGrid As GlacialComponents.Controls.GlacialList)

      FProgress = AProgress
      FTaskGrid = ATaskGrid

    End Sub

    Public Sub ChangeProgress()

      'Only called by OnFinish
      SyncLock Me
        FProgress.Invoke(New MethodInvoker(AddressOf ThreadSafeChangeProgress))
      End SyncLock


    End Sub

    Private Sub ThreadSafeChangeProgress()

      FProgress.PerformStep()
      FProgress.Refresh()

    End Sub

    Public Sub ChangeTaskGrid( _
                          ByVal ATask As String, _
                          ByVal AStatus As String, _
                          Optional ByVal AStart As Date = #1/1/1900#, _
                          Optional ByVal AFinish As Date = #1/1/1900#, _
                          Optional ByVal APosition As Integer = -1)

      'Called by everything
      SyncLock Me
        FTask = ATask
        FStatus = AStatus
        FStart = AStart
        FFinish = AFinish
        FPosition = APosition
        FTaskGrid.Invoke(New MethodInvoker(AddressOf ThreadSafeChangeTaskGrid))
      End SyncLock

    End Sub

    Private Sub ThreadSafeChangeTaskGrid()

      Dim found As Boolean
      found = False
      For a As Integer = 0 To FTaskGrid.Items.Count - 1
        If FTaskGrid.Items(a).Text = FTask Then
          If FStatus = "Finished" Then
            If FTaskGrid.Items(a).SubItems(1).Text <> "Started" Then
              FTaskGrid.Items(a).SubItems(1).Text = "Finished " + FTaskGrid.Items(a).SubItems(1).Text
            Else
              FTaskGrid.Items(a).SubItems(1).Text = FStatus
            End If
          Else
            FTaskGrid.Items(a).SubItems(1).Text = FStatus
          End If
          If FStart <> FDefaultDate Then FTaskGrid.Items(a).SubItems(2).Text = FStart.ToString("hh:mm:ss")
          If FFinish <> FDefaultDate Then
            FTaskGrid.Items(a).SubItems(3).Text = FFinish.ToString("hh:mm:ss")
            'add duration code here
          End If
          FTaskGrid.Items(a).Selected = True
          FTaskGrid.EnsureVisible(a)
          found = True
        End If
      Next
      If Not found Then
        Dim dtlm As New GlacialComponents.Controls.GLItem
        With dtlm
          .SubItems(0).Text = FTask
          .SubItems(1).Text = FStatus
          If FStart <> FDefaultDate Then .SubItems(2).Text = FStart.ToString("hh:mm:ss")
          If FFinish <> FDefaultDate Then .SubItems(3).Text = FFinish.ToString("hh:mm:ss")
        End With
        FTaskGrid.Items.Add(dtlm)
        dtlm.Selected = True
        FTaskGrid.EnsureVisible(FTaskGrid.Items.Count - 1)
      End If
      FTaskGrid.Refresh()
      FTaskGrid.Focus()

    End Sub

  End Class
[included for our Belgian friend]
End Module
[/included for our Belgian friend][smile]

Its definitely not perfect, which is why I keep playing with it. I'd be interested in your comments.
 
I'm pretty sure our code does almost the exact same thing. Your code adds a Sync Lock so that nothing else can update the GUI at the same time, which would be a good addition to my code. I want to check on something Chiph and RG discussed awhile ago about Sync Lock vs locking a 0 length array.

The other part acheives the same thing. I had to look up the MethodInvoker command. It looks like it creates a default delegate for you. So by using it, you don't have to declare the delegate seperatly.


SyncLock Me
FProgress.Invoke(New MethodInvoker(AddressOf ThreadSafeChangeProgress))
End SyncLock

After seeing that code, I'll update my code on tuesday to be more along these lines:

Code:
'private shared empty array to lock on
private shared LockObject() as object

'The private sub that acutally updates the status bar
Private Sub _SetStatusBarText(ByVal value As String)
  sbrMessages.Panels(0).Text = value
End Sub

'the public method that is called to update the status bar
Public Sub SetStatusBarText(ByVal value As String)
  If Me.InvokeRequired Then
    SyncLock LockObject
      Dim sArgs() As String = {value}
      Me.BeginInvoke(New MethodInvoker(AddressOf _SetStatusBarText), sArgs)
    End SyncLock
  Else
    _SetStatusBarText(value)
  End If
End Sub

That is completely untested, and I found a website with some good threading info (in C#):
-Rick

VB.Net Forum forum796 forum855 ASP.NET Forum
[monkey]I believe in killer coding ninja monkeys.[monkey]
 
ThatRickGuy -
Sorry I'm so late replying -- I just saw this thread. My only suggestions would be that you shouldn't need an array of strings to use in your invocation of the delegate instance, and that your synclock variable doesn't need the parenthesis -- just having it as a New Object should be enough. But double-check this with MSDN, as I'm more of a C# person.

earthandfire -
For your code, my suggestion would be that instead of using multiple private variables to pass values to your delegate, that you go ahead and create a new class to hold them. You'll see where Microsoft has done this in the framework by creating classes such as System.IO.FileSystemEventArgs. If your class inherited from EventArgs like this one does (and about 50 others in the framework!), that would give you more flexibility in reusing it. For example, you could pass one to a button_click event if you needed to hand some values to it from another thread.

Chip H.


____________________________________________________________________
If you want to get the best response to a question, please read FAQ222-2244 first
 
Good idea on the EventArgs Chiph! I was thinking the 2nd argument for .BeginInvoke was an array of Object. For some reason I was thinking RG was talking about using an empty array for a lock variable, as opposed to an uninstantiated object. Unfortunatly the search function is about useless again, so I can't find that thread. A star for you too!

-Rick

VB.Net Forum forum796 forum855 ASP.NET Forum
[monkey]I believe in killer coding ninja monkeys.[monkey]
 
Chiph, I didn't like the inflexibility of passing all the variables but just couldn't see a way around it. After more than a dozen crashes whilst trying to get the threads to talk to each other I was just grateful that I'd actually got something to work. As I said earlier, I've not finished with the code and I keep making minor changes to it.

Your suggestion should get rid of a lot of the clutter in the code and as you say make it more flexible. There is another routine in the worker thread that reports when it has finished, that I've currently commented out. Instead I use a timer in the UI thread to check the worker's status, as this was simpler than adding another set of routines to handle UI changes on thread termination. With the flexibility from the changes you've suggested, I'll be able to handle the cleanup code more easily and keep it all together.

Thanks, and I'm doubling Rick's star.
 
E&F you should look into a call back method. It's a way to launch another procedure at the completion of the thread. A handy way to do just what you mentioned (updating a GUI after a worker thread completes)

-Rick

VB.Net Forum forum796 forum855 ASP.NET Forum
[monkey]I believe in killer coding ninja monkeys.[monkey]
 
Rick, thats one think I like about TT. I post an answer to a thread and end up being spoilt for choice with solutions/alternatives to my code.

I've not used callbacks since I wrote macros in AmiPro (I think) when they were required and that was a good few years ago! (see OPSP) [smile] I didn't even give them a thought.

By the time I've rewritten my code based on what Chiph and you have suggested, I will end up a pretty much generic ThreadWrapperClass, a more or less generic WorkerClass (just plug in specific implementation) and a generic UIUpdateClass.

Threading made easy. Thanks, and I nearly didn't reply to this thread. Your star is on its way.
 
E&F, we've done pretty much the same thing here. I have an abstract base "ThreadTracker" class which handles the brunt of the threading. It gets inherited into 3 classes, BaseProcess(for engine code), BaseWordOutput(For Word Merge processes) and BaseCrystalOutput(For Crystal Reports processes). Then there are a slew of actual engines/reports/letters that inherit from them. But by the time I get to writing the code to assemble a data file or XML for Word/Crystal, it's just one sub and some parameters and it's done. I don't have to worry about re-writing the threading code or interactions with the GUI.

I am going to be doing some cleanup in there after reading this thread though :)

I will always jump at the chance to go over threading and design here on TT, lots of great minds, and people with experience.


On a slightly related topic. How do you handle early exits? One of the things I've been avoiding is having an 'exit' flag. It seems silly to me that if the user hit's cancel that the thread should wait until it completes the current activity (expecially if that activity is a cartesian join in a dataadapter.fill!)

So what I do is in the base ThreadTracker class, the first line of code is to set a class level thread variable equal to the current thread. I also have a public .Abort method that calls the thread variable's .Abort method. That pops a ThreadAbortException and stops the thread imediatly. With good cleanup in the Finally block, I get a fast response to the user's cancel request, and no memory leaks. But I've seen "iffy" reviews of using this method. Any opinions?

-Rick



VB.Net Forum forum796 forum855 ASP.NET Forum
[monkey]I believe in killer coding ninja monkeys.[monkey]
 
Status
Not open for further replies.

Part and Inventory Search

Sponsor

Back
Top