Welcome Guest ( Log In | Register )



 
Closed TopicStart new topic
> Multithreaded Listview Control In Different Classes Problem
tansqrx
post Feb 24 2006, 03:21 AM
Post #1


Super Member
Group Icon

Group: [HOSTED]
Posts: 557
Joined: 25-April 05
Member No.: 4,374
myCENTs:17.04



I maybe crossing into the advanced questions on this one but I have been working on this problem for a little over a week now and I am just plain stuck.

I have a windows application in VB.NET. The main form has a listview with approx 15 items and five subitems in each item. The list view acts as a display grid for data. The application performs many operations at once so naturally I have made it threaded to keep the main form responsive. The threads are running in their own classes. To give you a general idea see below.

CODE

Public Class frmMain

Dim loginThread As New Thread(AddressOf modBotControler. dosomething)
loginThread.start

End class    



Public Class modBotControler

Sub dosomething()


End sub

End class


Now I need to update the listview from the thread in dosomething(). At first I just passed a variable as a property into the modBotControler class. This worked fine except the program would crash for no good reason sparatically. I moved to Visual Studio 2005 and found the error of my way (at least I hope so). Apparently you can not change the value of a windows control from a different thread than it was created on. Research showed that MSDN offered a solution. ms-help://MS.VSExpressCC.v80/MS.NETFramework.v20.en/dv_fxmclictl/html/138f38b6-1099-4fd5-910c-390b41cbad35.htm

The solution uses a delegate to move the operation to the thread that the main form is on.

CODE

' This event handler creates a thread that calls a
' Windows Forms control in a thread-safe way.
Private Sub setTextSafeBtn_Click( _
ByVal sender As Object, _
ByVal e As EventArgs) Handles setTextSafeBtn.Click

     Me.demoThread = New Thread( _
     New ThreadStart(AddressOf Me.ThreadProcSafe))

     Me.demoThread.Start()
End Sub


' This method is executed on the worker thread and makes
' a thread-safe call on the TextBox control.
Private Sub ThreadProcSafe()
   Me.SetText("This text was set safely.")
End Sub


' This method demonstrates a pattern for making thread-safe
' calls on a Windows Forms control.
'
' If the calling thread is different from the thread that
' created the TextBox control, this method creates a
' SetTextCallback and calls itself asynchronously using the
' Invoke method.
'
' If the calling thread is the same as the thread that created
' the TextBox control, the Text property is set directly.

Private Sub SetText(ByVal [text] As String)

     ' InvokeRequired required compares the thread ID of the
     ' calling thread to the thread ID of the creating thread.
     ' If these threads are different, it returns true.
     If Me.textBox1.InvokeRequired Then
         Dim d As New SetTextCallback(AddressOf SetText)
         Me.Invoke(d, New Object() {[text]})
     Else
         Me.textBox1.Text = [text]
     End If
End Sub


Now I am sure that this works wonderful if you have everything thing in one class but I do not. One requirement is that the SetText function be private. You will also notice that when SetText is called, Me.SetText("This text was set safely."), that they used the Me operator which I can not do outside the current class. I have tried passing a reference to the main form and calling it that way, didn’t work. I have tried passing a reference to the SetTextDelegate, didn’t work. I have also tried about 100 other different things, didn’t work. If anyone has ever been in this sisutiton, please let me know what you did to fix it. BTW, the example uses a textbox and I am using a listview. Does that make a difference?
Go to the top of the page
 
+Quote Post
miCRoSCoPiC^eaRt...
post Feb 24 2006, 04:38 AM
Post #2


PsYcheDeLiC dR3aMeR
Group Icon

Group: Admin
Posts: 2,242
Joined: 29-January 05
From: Nakorn Chaisri, Thailand
Member No.: 2,411
myCENTs:84.36



Am off to Bangkok for the weekend. Let me ponder over this a bit - and I'll come back and post some solution smile.gif
Go to the top of the page
 
+Quote Post
saga
post Feb 24 2006, 07:58 AM
Post #3


Member [ Level 2 ]
Group Icon

Group: Members
Posts: 68
Joined: 23-May 05
Member No.: 5,355



how about use a global variable as flags. I mean first have a timer that checks this global variable every once in a while, lets say half a second then depending on the value of the global variable update or do not update the list view. I don't think using a timer will slow down other threads in the process. I use this technique once, a program loader, it works for me. It didnt slow the process or didnt even consume much of the precious CPU.
Go to the top of the page
 
+Quote Post
tansqrx
post Feb 24 2006, 09:04 AM
Post #4


Super Member
Group Icon

Group: [HOSTED]
Posts: 557
Joined: 25-April 05
Member No.: 4,374
myCENTs:17.04



At long last I have found a solution to my threading problems. And all I can say is that I strongly believe that I was a victim of code gremlins. I just moved to Visual Studio 2005 where I first realized that I even had a problem. After literally trying 100’s of different combinations I just started throwing stuff at the wall to see what would stick. In a last ditch effort, I simplified a sub by commenting out a try/catch and putting the call to the listview just after the sub beginning. To my absolute surprise it worked. I uncommented the try/catch and it still worked. I didn’t change any meaningful code and the **** program worked.

After a few hours of consideration, in the end I think I hit a Visual Studio bug. The reason that I say this is while fooling around with the dLVMain function, see code later, every now and again I would get one of those green squiggly lines out of no where. Looking at the function, nothing had changed and when I moused over the error, Visual Studio would crash. I didn’t think much of it at the time but later on I have a feeling that this might have been part of the problem. I don’t know about you but that is the one thing that gets me going. Working on a problem that you look at for a week knowing that it should work, only to find out that it is a **** bug. Ohh well what can you do.

Saga, I did consider a similar work around earlier but I had to rule it out. The listview is generated dynamically and can have any number of elements. This can lead to a problem allocating all the static variables (can be in the 100’s). I really hate the idea of using timers in this fashion. The point of using .NET is so I do not have to resort to resource consuming timers. Everything by nature should be event driven. Implementing this solution may also hold more hidden problems. Another possibility that I considered was to raise an event and then set the listview values. I soon realized that the event is raised in the thread that called the event and not in the thread that holds the windows form thus the problem is not solved. Although untested, I am afraid using timers would pose the same problem.

Enough with the *****ing, here is the solution. The result is a little testing along with examples from ms-help://MS.VSExpressCC.v80/MS.NETFramework.v20.en/dv_fxmclictl/html/138f38b6-1099-4fd5-910c-390b41cbad35.htm and Programming Microsoft Visual Basic.NET 2003 by Francesco Balena [ http://www.dotnet2themax.com/BooksFrancesco.aspx ]. I have to say that Balena’s book is one of the best programming books that I have ever had the privilege of owning. If you do VB.NET then you need this book. Balena suggested adding a separate variable to call the delegate that was setup in the MS documentation. I will use the example that I used in my first post and fill in what was needed.

CODE
Public Class frmMain

‘this is the delegate used in the MS documentation
Delegate Sub dLVMain(ByVal row As Integer, ByVal col As Integer, ByVal text As String)
‘A second variable is added so that I can pass this reference to the other class
    Dim setLVMainMarshaler As dLVMain = AddressOf Me.setLVMain

‘this is where the real work is.  If you place this in the debugger you can see that when
‘calling from a second thread, the if is true and then immediately afterwards it is fasle
‘because the invoke moves the call to the correct thread.  Mostly from MS documentation
    Public Sub setDgMain(ByVal row As Integer, ByVal col As Integer, ByVal value As String)
        If Me.dgMain.InvokeRequired Then
            Dim d As New dDgMain(AddressOf setDgMain)
            Me.Invoke(d, New Object() {row, col, value})
        Else
            SyncLock listviewLock
                dgMain(row, col) = value
                'dgMain(0, 2) = "hello"
            End SyncLock
        End If
    End Sub

Dim loginThread As New Thread(AddressOf modBotControler. dosomething)
‘pass the delegate variable to the other class
loginThread.setLVMainMarshaler = setLVMainMarshaler
loginThread.start

End class    



Public Class modBotControler

‘setup a property to get a reference to the delegate
Private _setLVMainMarshaler As frmMain.dLVMain

    Public Property setLVMainMarshaler() As frmMain.dLVMain

        Get
            Return _setLVMainMarshaler
        End Get
        Set(ByVal Value As frmMain.dLVMain)
            _setLVMainMarshaler = Value
        End Set
    End Property

Sub dosomething()

_setLVMainMarshaler(_ClassID, 3, "Login Sucess")

End sub

End class



I am not completely sure that this is the best solution but this seems like the logical divertive that MS would like you to use. I hope someone else can use this information as it is a weeks worth of work, lol. Ohh yeah, **** code gremlins.
Go to the top of the page
 
+Quote Post

Closed TopicStart new topic

Collapse

> Similar Topics

Topics Topics


 



- Lo-Fi Version Time is now: 5th December 2008 - 01:14 AM