|
|
|
|
![]() ![]() |
Feb 24 2006, 03:21 AM
Post
#1
|
|
|
Super Member 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? |
|
|
|
Feb 24 2006, 04:38 AM
Post
#2
|
|
|
PsYcheDeLiC dR3aMeR 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
|
|
|
|
Feb 24 2006, 07:58 AM
Post
#3
|
|
|
Member [ Level 2 ] 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.
|
|
|
|
Feb 24 2006, 09:04 AM
Post
#4
|
|
|
Super Member 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. |
|
|
|
![]() ![]() |
Similar Topics
| Topics | Topics | |
|---|---|---|
|
|
|
Lo-Fi Version | Time is now: 5th December 2008 - 01:14 AM |