sidwelle
asked on
Update listbox from another thread
I am trying to update a listbox from another thread, but I never see ..invokerequired return 'true' and then then when it updates the control, it appears to be loading a new instance of the form.
Public Delegate Function DelListEvent(ByVal Msg As String) As Integer
Public Function ListEvent(ByVal Msg As String) As Integer
Dim iRet As Integer = 0
Dim sDS As String = ""
If lstEvents.InvokeRequired Then
'iRet = lstEvents.Invoke(New MethodInvoker(AddressOf ListEvent))
Dim d As New DelListEvent(AddressOf ListEvent)
'lstEvents.BeginInvoke(New DelListEvent(AddressOf ListEvent), Msg)
lstEvents.BeginInvoke(d, New Object() {Msg})
Else
sDS = Now.ToString("yy/MM/dd HH:mm:ss.fff")
Me.lstEvents.Items.Add(sDS & " > " & Msg)
Application.DoEvents()
End If
End Function
ASKER
its going to take me a day to read all that out.
Thank you for the reply, but I don't see how line 27 from your first code block in different from my line 13 ? I am still 'Invoking' the listbox and passing a value.
When I step through it line by line, I see it go through from_load like its loading a new instance of the form that you can't see ?!
Then it goes through the ListEvent Procedure, the Invokerequired is false and it adds the item to the listbox w/o errors.
But there are no new items visible in the box !
Did it create a new instance of the form that not show ?
Thanks
Thank you for the reply, but I don't see how line 27 from your first code block in different from my line 13 ? I am still 'Invoking' the listbox and passing a value.
When I step through it line by line, I see it go through from_load like its loading a new instance of the form that you can't see ?!
Then it goes through the ListEvent Procedure, the Invokerequired is false and it adds the item to the listbox w/o errors.
But there are no new items visible in the box !
Did it create a new instance of the form that not show ?
Thanks
The first difference that I see is that your delegate and method do not have the control as a parameter. Let's attack this from the opposite path, where are you calling your method from?
-saige-
-saige-
ASKER
Why, the control is on the form. Can't I just call the control by its name w/the form name ?
Me.lstEvents.Items.Add... 'what's wrong with this ?
Me.lstEvents.Items.Add... 'what's wrong with this ?
ASKER
I am calling the procedure on the form from a function a thread that I started.
Tried passing a reference to the from in via the constructor of the class, but that didn't work either.
when I hover over the reference, I see a property :
"AccessibilityObject = {"Cross-thread operation not valid: Control 'frmListener' accessed from a thread other than the thread it was created on."}"
Tried passing a reference to the from in via the constructor of the class, but that didn't work either.
when I hover over the reference, I see a property :
"AccessibilityObject = {"Cross-thread operation not valid: Control 'frmListener' accessed from a thread other than the thread it was created on."}"
You need to tell the system to not check for cross thread as you state your proble.
"AccessibilityObject = {"Cross-thread operation not valid: Control 'frmListener' accessed from a thread other than the thread it was created on."}"Try to add this at your form_load as well as at your function at the very top of your function and see.
Control.CheckForIllegalCrossThreadCalls = False
ASKER
I added the above line to "form_shown".
Also, instead of passing a reference to the parent form, I passed a reference to the listbox and was able to add items to it directly.
But not sure if this is correct, why can I not call the function "ListEvents(...)" and have it perform the task for me ?
Also, instead of passing a reference to the parent form, I passed a reference to the listbox and was able to add items to it directly.
But not sure if this is correct, why can I not call the function "ListEvents(...)" and have it perform the task for me ?
Why did you do this?
Public Delegate Function DelListEvent(ByVal Msg As String) As Integer
Public Function ListEvent(ByVal Msg As String) As Integer
ASKER
I used a delegate to make the call thread safe ?
You *can* use your method. I showed this in my examples. There are also many articles discussing using delegates to perform tasks on controls; It is not an uncommon request.
As I cannot reproduce your issue directly, why don't you put together a really simple example that shows the issue you are having.
-saige-
As I cannot reproduce your issue directly, why don't you put together a really simple example that shows the issue you are having.
-saige-
ASKER
My issue is that I never see the control update the item that I added, when its added from the other thread. Tried using a delegate, tried background worker.
The only way I can get it work is to pass a reference to the thread and then use that reference to update the control. Very much like what you did by passing the "LB".
I have never seen the controls "InvokeRequired" property return 'true'
But the control updates. Is this thread safe ?
The only way I can get it work is to pass a reference to the thread and then use that reference to update the control. Very much like what you did by passing the "LB".
I have never seen the controls "InvokeRequired" property return 'true'
But the control updates. Is this thread safe ?
ASKER
Put it all back and included the way that you did it, don't know if it will ever use the delegate ?
Works now, but I don't understand why I have to pass the reference all around ?
Works now, but I don't understand why I have to pass the reference all around ?
If your control updates, then you are not trying to update from a different thread. If you do not Invoke from a different thread you will get a Cross-Thread exception thrown.
Proof of concept -
We know my code above works because the example clearly shows where invoke is being called via the messages that are written to the console. What happens if I don't use the delegate:
Form1.vb -
Let's correct this:
Form1.vb -
Now gives us the expected output (notice without even using a delegate).
-saige-
Proof of concept -
We know my code above works because the example clearly shows where invoke is being called via the messages that are written to the console. What happens if I don't use the delegate:
Form1.vb -
Imports System.Threading.Tasks
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class Form1
Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
If TypeOf sender Is Button Then
Dim btn = DirectCast(sender, Button)
If btn.Equals(Button1) Then
Task.Factory.StartNew(Sub()
For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
ListBox1.Items.Add([Item])
Next
End Sub)
ElseIf btn.Equals(Button2) Then
Task.Factory.StartNew(Sub()
For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
ListBox1.Items.Remove([Item])
Next
End Sub)
End If
End If
End Sub
End Class
Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.ListBox1 = New System.Windows.Forms.ListBox()
Me.Button1 = New System.Windows.Forms.Button()
Me.Button2 = New System.Windows.Forms.Button()
Me.SuspendLayout()
'
'ListBox1
'
Me.ListBox1.FormattingEnabled = True
Me.ListBox1.Location = New System.Drawing.Point(13, 13)
Me.ListBox1.Name = "ListBox1"
Me.ListBox1.Size = New System.Drawing.Size(259, 212)
Me.ListBox1.TabIndex = 0
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(96, 226)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(95, 23)
Me.Button1.TabIndex = 1
Me.Button1.Text = "Add 20 Items"
Me.Button1.UseVisualStyleBackColor = True
'
'Button2
'
Me.Button2.Location = New System.Drawing.Point(197, 226)
Me.Button2.Name = "Button2"
Me.Button2.Size = New System.Drawing.Size(75, 23)
Me.Button2.TabIndex = 2
Me.Button2.Text = "Clear"
Me.Button2.UseVisualStyleBackColor = True
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 261)
Me.Controls.Add(Me.Button2)
Me.Controls.Add(Me.Button1)
Me.Controls.Add(Me.ListBox1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents Button2 As System.Windows.Forms.Button
End Class
InvalidOperationException caused by an invalid Cross-thread operation -Let's correct this:
Form1.vb -
Imports System.Threading.Tasks
Imports System.ComponentModel
Imports System.Runtime.CompilerServices
Public Class Form1
Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
If TypeOf sender Is Button Then
Dim btn = DirectCast(sender, Button)
If btn.Equals(Button1) Then
Task.Factory.StartNew(Sub()
For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
If ListBox1.InvokeRequired Then
Console.WriteLine("Invoking the additon of {0}", [Item])
ListBox1.Invoke(New MethodInvoker(Sub()
Console.WriteLine("Adding {0}; to ListBox", [Item])
ListBox1.Items.Add([Item])
End Sub))
Else
Console.WriteLine("Adding {0}; to ListBox", [Item])
ListBox1.Items.Add([Item])
End If
Next
End Sub)
ElseIf btn.Equals(Button2) Then
Task.Factory.StartNew(Sub()
For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
If ListBox1.InvokeRequired Then
Console.WriteLine("Invoking the removal of {0}", [Item])
ListBox1.Invoke(New MethodInvoker(Sub()
Console.WriteLine("Removing {0}; from ListBox", [Item])
ListBox1.Items.Remove([Item])
End Sub))
Else
Console.WriteLine("Removing {0}; from ListBox", [Item])
ListBox1.Items.Remove([Item])
End If
Next
End Sub)
End If
End If
End Sub
End Class
Module Extensions
<Extension()> _
Public Sub HandleInvokeRequired(Of T As {Control, ISynchronizeInvoke})(control As T, action As Action(Of T))
'Check to see is the control is not null
If control Is Nothing Then
Throw New ArgumentNullException(String.Format("Cannot execute {0} on {1}. {1} is null.", action, control))
End If
'Check to see if the control is disposed.
If TypeOf control Is Control AndAlso TryCast(control, Control).IsDisposed Then
Throw New ObjectDisposedException(String.Format("Cannot execute {0} on {1}. {1} is disposed.", action, control))
End If
'Check to see if the handle is created for the control.
If TypeOf control Is Control AndAlso Not TryCast(control, Control).IsHandleCreated Then
Throw New InvalidOperationException(String.Format("Cannot execute {0} on {1}. Handle is not created for {1}.", action, control))
End If
'Check to see if the control's InvokeRequired property is true
If control.InvokeRequired Then
Try
'Use Invoke() to invoke your action
Console.WriteLine("Invoking the action {0}; on {1}", action, control)
control.Invoke(action, New Object() {control})
Catch ex As Exception
Throw New Exception(String.Format("Cannot execute {0} on {1}. {2}.", action, control, ex.Message))
End Try
Else
Try
'Perform the action
Console.WriteLine("Performing the action {0}; on {1}", action, control)
action(control)
Catch ex As Exception
Throw New Exception(String.Format("Cannot execute {0} on {1}. {2}.", action, control, ex.Message))
End Try
End If
End Sub
End Module
Now gives us the expected output (notice without even using a delegate).
-saige-
ASKER
it_saige: Do you recommend the following statement recommended my MCI on this question:
Control.CheckForIllegalCro ssThreadCa lls = False
Also, I am using an older version of VB.Net 8. It does not recognize "Imports System.Threading.Tasks"
Do I need a new version ? it this what I am fighting ?
Control.CheckForIllegalCro
Also, I am using an older version of VB.Net 8. It does not recognize "Imports System.Threading.Tasks"
Do I need a new version ? it this what I am fighting ?
No I don't recommend using the recommendation by MCI. You are not solving the issue, you are simply stopping the exception from being thrown. The whole purpose of the exception is to tell you that something is wrong; in this case the problem, in a nutshell, is that two (or more) threads are being allowed to access and update a control at the same time. This is a problem because now you have no guarantee as to the state of the control.
The thread that created the control may think that the control is disabled while any of your additional threads believe that the control is enabled; Or worse, the thread that created the handle for the control closes thereby disposing of the control handle, now one of your additional threads try to access, whoops Object disposed exception (or vice versa). In these cases, you are experiencing concurrency discrepancies caused by race conditions.
To demonstrate race conditions let's take our original application and add a race button to it -
Form1.vb -
First run -Second run -Third run -As you can see on two of the runs we got the expected results, but one run gave us unexpected results. Also take note of the thread ordering (not that it matters for our situation because 11 state changes should ultimately result in each button being enabled from an initial disabled state).
What about with using the Invoke method, does this correct our inconsistent results? Let's find out:
Form1.vb -
As for the second part of your question, your assumption is correct in that System.Threading.Tasks was not made available until .NET 4.0. VB8 (released as Visual Basic 2005) does not support .NET 4.0. It does, however, support .NET 2.0 and .NET 3.0 (with an update if I remember correctly).
That being said, the following example should work for you in VS 2005/VB 2005 -
Form1.vb -
The thread that created the control may think that the control is disabled while any of your additional threads believe that the control is enabled; Or worse, the thread that created the handle for the control closes thereby disposing of the control handle, now one of your additional threads try to access, whoops Object disposed exception (or vice versa). In these cases, you are experiencing concurrency discrepancies caused by race conditions.
To demonstrate race conditions let's take our original application and add a race button to it -
Form1.vb -
Imports System.Threading.Tasks
Imports System.ComponentModel
Public Class Form1
Private Sub OnClick(sender As Object, e As EventArgs) Handles Button3.Click, Button2.Click, Button1.Click
If TypeOf sender Is Button Then
Dim btn = DirectCast(sender, Button)
If btn.Equals(Button1) Then
Task.Factory.StartNew(Sub()
For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
If ListBox1.InvokeRequired Then
Console.WriteLine("Invoking the additon of {0}", [Item])
ListBox1.Invoke(New MethodInvoker(Sub()
Console.WriteLine("Adding {0}; to ListBox", [Item])
ListBox1.Items.Add([Item])
End Sub))
Else
Console.WriteLine("Adding {0}; to ListBox", [Item])
ListBox1.Items.Add([Item])
End If
Next
End Sub)
ElseIf btn.Equals(Button2) Then
Task.Factory.StartNew(Sub()
For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
If ListBox1.InvokeRequired Then
Console.WriteLine("Invoking the removal of {0}", [Item])
ListBox1.Invoke(New MethodInvoker(Sub()
Console.WriteLine("Removing {0}; from ListBox", [Item])
ListBox1.Items.Remove([Item])
End Sub))
Else
Console.WriteLine("Removing {0}; from ListBox", [Item])
ListBox1.Items.Remove([Item])
End If
Next
End Sub)
ElseIf btn.Equals(Button3) Then
Dim updaters As New List(Of Task)
For i = 0 To 10
Dim j As Integer = i
updaters.Add(Task.Factory.StartNew(Sub() UpdateButtons(j + 1)))
Next
Task.WaitAll(updaters.ToArray())
Console.WriteLine("Main Thread: Button1: {0}; Button2: {1}", If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
End If
End If
End Sub
Sub UpdateButtons(i As Integer)
Button1.Enabled = Not Button1.Enabled
Button2.Enabled = Not Button2.Enabled
Console.WriteLine("Thread {0}: Button1: {1}; Button2: {2}", i, If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
End Sub
Private Sub OnLoad(sender As Object, e As EventArgs) Handles MyBase.Load
Button1.Enabled = False
Button2.Enabled = False
Console.WriteLine("Main Thread: Button1: {0}; Button2: {1}", If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
CheckForIllegalCrossThreadCalls = False
End Sub
End Class
Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.ListBox1 = New System.Windows.Forms.ListBox()
Me.Button1 = New System.Windows.Forms.Button()
Me.Button2 = New System.Windows.Forms.Button()
Me.Button3 = New System.Windows.Forms.Button()
Me.SuspendLayout()
'
'ListBox1
'
Me.ListBox1.FormattingEnabled = True
Me.ListBox1.Location = New System.Drawing.Point(13, 13)
Me.ListBox1.Name = "ListBox1"
Me.ListBox1.Size = New System.Drawing.Size(259, 212)
Me.ListBox1.TabIndex = 0
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(96, 226)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(95, 23)
Me.Button1.TabIndex = 1
Me.Button1.Text = "Add 20 Items"
Me.Button1.UseVisualStyleBackColor = True
'
'Button2
'
Me.Button2.Location = New System.Drawing.Point(197, 226)
Me.Button2.Name = "Button2"
Me.Button2.Size = New System.Drawing.Size(75, 23)
Me.Button2.TabIndex = 2
Me.Button2.Text = "Clear"
Me.Button2.UseVisualStyleBackColor = True
'
'Button3
'
Me.Button3.Location = New System.Drawing.Point(12, 226)
Me.Button3.Name = "Button3"
Me.Button3.Size = New System.Drawing.Size(75, 23)
Me.Button3.TabIndex = 3
Me.Button3.Text = "Race"
Me.Button3.UseVisualStyleBackColor = True
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 261)
Me.Controls.Add(Me.Button3)
Me.Controls.Add(Me.Button2)
Me.Controls.Add(Me.Button1)
Me.Controls.Add(Me.ListBox1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents Button2 As System.Windows.Forms.Button
Friend WithEvents Button3 As System.Windows.Forms.Button
End Class
The expectation is that when we click the race button that both the add and clear buttons are enabled. The results, however, are hardly the expectation:First run -Second run -Third run -As you can see on two of the runs we got the expected results, but one run gave us unexpected results. Also take note of the thread ordering (not that it matters for our situation because 11 state changes should ultimately result in each button being enabled from an initial disabled state).
What about with using the Invoke method, does this correct our inconsistent results? Let's find out:
Form1.vb -
Imports System.Threading.Tasks
Imports System.ComponentModel
Public Class Form1
Private Sub OnClick(sender As Object, e As EventArgs) Handles Button3.Click, Button2.Click, Button1.Click
If TypeOf sender Is Button Then
Dim btn = DirectCast(sender, Button)
If btn.Equals(Button1) Then
Task.Factory.StartNew(Sub()
For Each [Item] In (From i In Enumerable.Range(ListBox1.Items.Count, 20) Select String.Format("Message{0}", i))
If ListBox1.InvokeRequired Then
Console.WriteLine("Invoking the additon of {0}", [Item])
ListBox1.Invoke(New MethodInvoker(Sub()
Console.WriteLine("Adding {0}; to ListBox", [Item])
ListBox1.Items.Add([Item])
End Sub))
Else
Console.WriteLine("Adding {0}; to ListBox", [Item])
ListBox1.Items.Add([Item])
End If
Next
End Sub)
ElseIf btn.Equals(Button2) Then
Task.Factory.StartNew(Sub()
For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
If ListBox1.InvokeRequired Then
Console.WriteLine("Invoking the removal of {0}", [Item])
ListBox1.Invoke(New MethodInvoker(Sub()
Console.WriteLine("Removing {0}; from ListBox", [Item])
ListBox1.Items.Remove([Item])
End Sub))
Else
Console.WriteLine("Removing {0}; from ListBox", [Item])
ListBox1.Items.Remove([Item])
End If
Next
End Sub)
ElseIf btn.Equals(Button3) Then
Dim updaters As New List(Of Task)
For i = 0 To 10
Dim j As Integer = i
updaters.Add(Task.Factory.StartNew(Sub() UpdateButtons(j + 1)))
Next
Task.WaitAll(updaters.ToArray())
Console.WriteLine("Main Thread: Button1: {0}; Button2: {1}", If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
End If
End If
End Sub
Sub UpdateButtons(i As Integer)
If Button1.InvokeRequired Then
Button1.BeginInvoke(New MethodInvoker(Sub() Button1.Enabled = Not Button1.Enabled))
Else
Button1.Enabled = Not Button1.Enabled
End If
If Button2.InvokeRequired Then
Button2.BeginInvoke(New MethodInvoker(Sub() Button2.Enabled = Not Button2.Enabled))
Else
Button2.Enabled = Not Button2.Enabled
End If
Console.WriteLine("Thread {0}: Button1: {1}; Button2: {2}", i, If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
End Sub
Private Sub OnLoad(sender As Object, e As EventArgs) Handles MyBase.Load
Button1.Enabled = False
Button2.Enabled = False
Console.WriteLine("Main Thread: Button1: {0}; Button2: {1}", If(Button1.Enabled, "Enabled", "Disabled"), If(Button2.Enabled, "Enabled", "Disabled"))
End Sub
End Class
First run -Second run -Third run -Consistency is obtained.As for the second part of your question, your assumption is correct in that System.Threading.Tasks was not made available until .NET 4.0. VB8 (released as Visual Basic 2005) does not support .NET 4.0. It does, however, support .NET 2.0 and .NET 3.0 (with an update if I remember correctly).
That being said, the following example should work for you in VS 2005/VB 2005 -
Form1.vb -
Imports System.Threading
Public Class Form1
Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
If TypeOf sender Is Button Then
Dim btn = DirectCast(sender, Button)
If btn.Equals(Button1) Then
Dim lbUpdater = New Thread(AddressOf AddItems)
lbUpdater.IsBackground = True
lbUpdater.Start()
ElseIf btn.Equals(Button2) Then
Dim lbUpdator = New Thread(AddressOf RemoveItems)
lbUpdator.IsBackground = True
lbUpdator.Start()
End If
End If
End Sub
Delegate Sub AddItemDelegate(lb As ListBox, message As String)
Delegate Sub RemoveItemDelegate(lb As ListBox, item As Object)
Sub AddItems()
For i As Integer = ListBox1.Items.Count To ListBox1.Items.Count + 20
AddItem(ListBox1, String.Format("Message{0}", i))
Next
End Sub
Sub RemoveItems()
For i As Integer = ListBox1.Items.Count - 1 To 0 Step -1
RemoveItem(ListBox1, ListBox1.Items(i))
Next
End Sub
Sub AddItem(lb As ListBox, message As String)
If lb.InvokeRequired Then
Console.WriteLine("Invoking the addition of {0}", message)
lb.Invoke(New AddItemDelegate(AddressOf AddItem), New Object() {lb, message})
Else
Console.WriteLine("Adding {0}; to Listbox", message)
lb.Items.Add(message)
End If
End Sub
Sub RemoveItem(lb As ListBox, item As Object)
If lb.InvokeRequired Then
Console.WriteLine("Invoking the removal of {0}", item)
lb.Invoke(New RemoveItemDelegate(AddressOf RemoveItem), New Object() {lb, item})
Else
Console.WriteLine("Removing {0}; from Listbox", item)
lb.Items.Remove(item)
End If
End Sub
End Class
Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.ListBox1 = New System.Windows.Forms.ListBox()
Me.Button1 = New System.Windows.Forms.Button()
Me.Button2 = New System.Windows.Forms.Button()
Me.SuspendLayout()
'
'ListBox1
'
Me.ListBox1.FormattingEnabled = True
Me.ListBox1.Location = New System.Drawing.Point(13, 13)
Me.ListBox1.Name = "ListBox1"
Me.ListBox1.Size = New System.Drawing.Size(259, 212)
Me.ListBox1.TabIndex = 0
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(96, 226)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(95, 23)
Me.Button1.TabIndex = 1
Me.Button1.Text = "Add 20 Items"
Me.Button1.UseVisualStyleBackColor = True
'
'Button2
'
Me.Button2.Location = New System.Drawing.Point(197, 226)
Me.Button2.Name = "Button2"
Me.Button2.Size = New System.Drawing.Size(75, 23)
Me.Button2.TabIndex = 2
Me.Button2.Text = "Clear"
Me.Button2.UseVisualStyleBackColor = True
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 261)
Me.Controls.Add(Me.Button2)
Me.Controls.Add(Me.Button1)
Me.Controls.Add(Me.ListBox1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents Button2 As System.Windows.Forms.Button
End Class
-saige-
ASKER
Your example is easy to follow, but I can't reproduce it because I never got beyond:
"Imports System.Threading.Tasks"
"Does not contain a public member and cannot be found"
Do I need to use another version of VB other than 8 ?
"Imports System.Threading.Tasks"
"Does not contain a public member and cannot be found"
Do I need to use another version of VB other than 8 ?
Yes, in order to use the code dependent upon System.Threading.Tasks, you need VB 10 (Visual Studio 2010) or higher, targeting .NET 4.0 or higher. The most current version, VS 2015, is available in a community edition for free if you qualify.
https://www.visualstudio.com/products/visual-studio-community-vs
-saige-
https://www.visualstudio.com/products/visual-studio-community-vs
-saige-
ASKER
Can I get it done with using vb 8 using .Net 3.5 Using Delegates ?
That's currently what we have.
That's currently what we have.
It may have been lost in the message, but I did include a version that should work with VB 8 in my response here: https:/Q_28964240.html?anchor=a41765714#a41765639
Reposting just the compatible portion of the code:
Form1.vb -
Form1.Designer.vb -
-saige-
Reposting just the compatible portion of the code:
Form1.vb -
Imports System.Threading
Public Class Form1
Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
If TypeOf sender Is Button Then
Dim btn = DirectCast(sender, Button)
If btn.Equals(Button1) Then
Dim lbUpdater = New Thread(AddressOf AddItems)
lbUpdater.IsBackground = True
lbUpdater.Start()
ElseIf btn.Equals(Button2) Then
Dim lbUpdator = New Thread(AddressOf RemoveItems)
lbUpdator.IsBackground = True
lbUpdator.Start()
End If
End If
End Sub
Delegate Sub AddItemDelegate(lb As ListBox, message As String)
Delegate Sub RemoveItemDelegate(lb As ListBox, item As Object)
Sub AddItems()
For i As Integer = ListBox1.Items.Count To ListBox1.Items.Count + 20
AddItem(ListBox1, String.Format("Message{0}", i))
Next
End Sub
Sub RemoveItems()
For i As Integer = ListBox1.Items.Count - 1 To 0 Step -1
RemoveItem(ListBox1, ListBox1.Items(i))
Next
End Sub
Sub AddItem(lb As ListBox, message As String)
If lb.InvokeRequired Then
Console.WriteLine("Invoking the addition of {0}", message)
lb.Invoke(New AddItemDelegate(AddressOf AddItem), New Object() {lb, message})
Else
Console.WriteLine("Adding {0}; to Listbox", message)
lb.Items.Add(message)
End If
End Sub
Sub RemoveItem(lb As ListBox, item As Object)
If lb.InvokeRequired Then
Console.WriteLine("Invoking the removal of {0}", item)
lb.Invoke(New RemoveItemDelegate(AddressOf RemoveItem), New Object() {lb, item})
Else
Console.WriteLine("Removing {0}; from Listbox", item)
lb.Items.Remove(item)
End If
End Sub
End Class
Form1.Designer.vb -
<Global.Microsoft.VisualBasic.CompilerServices.DesignerGenerated()> _
Partial Class Form1
Inherits System.Windows.Forms.Form
'Form overrides dispose to clean up the component list.
<System.Diagnostics.DebuggerNonUserCode()> _
Protected Overrides Sub Dispose(ByVal disposing As Boolean)
Try
If disposing AndAlso components IsNot Nothing Then
components.Dispose()
End If
Finally
MyBase.Dispose(disposing)
End Try
End Sub
'Required by the Windows Form Designer
Private components As System.ComponentModel.IContainer
'NOTE: The following procedure is required by the Windows Form Designer
'It can be modified using the Windows Form Designer.
'Do not modify it using the code editor.
<System.Diagnostics.DebuggerStepThrough()> _
Private Sub InitializeComponent()
Me.ListBox1 = New System.Windows.Forms.ListBox()
Me.Button1 = New System.Windows.Forms.Button()
Me.Button2 = New System.Windows.Forms.Button()
Me.SuspendLayout()
'
'ListBox1
'
Me.ListBox1.FormattingEnabled = True
Me.ListBox1.Location = New System.Drawing.Point(13, 13)
Me.ListBox1.Name = "ListBox1"
Me.ListBox1.Size = New System.Drawing.Size(259, 212)
Me.ListBox1.TabIndex = 0
'
'Button1
'
Me.Button1.Location = New System.Drawing.Point(96, 226)
Me.Button1.Name = "Button1"
Me.Button1.Size = New System.Drawing.Size(95, 23)
Me.Button1.TabIndex = 1
Me.Button1.Text = "Add 20 Items"
Me.Button1.UseVisualStyleBackColor = True
'
'Button2
'
Me.Button2.Location = New System.Drawing.Point(197, 226)
Me.Button2.Name = "Button2"
Me.Button2.Size = New System.Drawing.Size(75, 23)
Me.Button2.TabIndex = 2
Me.Button2.Text = "Clear"
Me.Button2.UseVisualStyleBackColor = True
'
'Form1
'
Me.AutoScaleDimensions = New System.Drawing.SizeF(6.0!, 13.0!)
Me.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font
Me.ClientSize = New System.Drawing.Size(284, 261)
Me.Controls.Add(Me.Button2)
Me.Controls.Add(Me.Button1)
Me.Controls.Add(Me.ListBox1)
Me.Name = "Form1"
Me.Text = "Form1"
Me.ResumeLayout(False)
End Sub
Friend WithEvents ListBox1 As System.Windows.Forms.ListBox
Friend WithEvents Button1 As System.Windows.Forms.Button
Friend WithEvents Button2 As System.Windows.Forms.Button
End Class
-saige-
ASKER
Ok, found issue, changed:
Sub AddItem(lb As ListBox, message As String)
If lstBox.InvokeRequired Then 'My Line
Sub AddItem(lb As ListBox, message As String)
If lb.InvokeRequired Then 'To your line
This shows me how to update the controls, Now I need to get to vars on form module ?
Sub AddItem(lb As ListBox, message As String)
If lstBox.InvokeRequired Then 'My Line
Sub AddItem(lb As ListBox, message As String)
If lb.InvokeRequired Then 'To your line
This shows me how to update the controls, Now I need to get to vars on form module ?
In this case lb is the ListBox, but if you need form instance specific access, you should be able to use the FindForm method to find the form that the control is bound to; e.g. -
-saige-
Imports System.Threading
Public Class Form1
Private SomeText As String = "SomeText"
Private Sub OnClick(sender As Object, e As EventArgs) Handles Button2.Click, Button1.Click
If TypeOf sender Is Button Then
Dim btn = DirectCast(sender, Button)
If btn.Equals(Button1) Then
Dim lbUpdater = New Thread(AddressOf AddItems)
lbUpdater.IsBackground = True
lbUpdater.Start()
ElseIf btn.Equals(Button2) Then
Dim lbUpdator = New Thread(AddressOf RemoveItems)
lbUpdator.IsBackground = True
lbUpdator.Start()
End If
End If
End Sub
Delegate Sub AddItemDelegate(lb As ListBox, message As String)
Delegate Sub RemoveItemDelegate(lb As ListBox, item As Object)
Sub AddItems()
For i As Integer = ListBox1.Items.Count To ListBox1.Items.Count + 20
AddItem(ListBox1, String.Format("Message{0}", i))
Next
End Sub
Sub RemoveItems()
For i As Integer = ListBox1.Items.Count - 1 To 0 Step -1
RemoveItem(ListBox1, ListBox1.Items(i))
Next
End Sub
Sub AddItem(lb As ListBox, message As String)
If lb.InvokeRequired Then
Console.WriteLine("Invoking the addition of {0}", message)
lb.Invoke(New AddItemDelegate(AddressOf AddItem), New Object() {lb, message})
Else
Console.WriteLine("Adding {0}; to Listbox", message)
Dim instance = lb.FindForm()
Console.WriteLine(CType(instance, Form1).SomeText)
lb.Items.Add(message)
End If
End Sub
Sub RemoveItem(lb As ListBox, item As Object)
If lb.InvokeRequired Then
Console.WriteLine("Invoking the removal of {0}", item)
lb.Invoke(New RemoveItemDelegate(AddressOf RemoveItem), New Object() {lb, item})
Else
Console.WriteLine("Removing {0}; from Listbox", item)
lb.Items.Remove(item)
End If
End Sub
End Class
-saige-
ASKER
it_saige, still working on this project. its just a little busy here.
I plan on posting a mock of project demoing the issue in a day or two.
Thank you.
I plan on posting a mock of project demoing the issue in a day or two.
Thank you.
Thanks for the update. No worries, been busy on this side of the internet as well. ;)
-saige-
-saige-
ASKER
Here is a mock up of the project.
For the most part it works.
Issues:
1. You have to pass a reference to the listbox (or control you want to update) to your new threads.
2. If you try to broadcast a banner on the new connection, the app becomes un-stable. (event just the first one!?)
3. when I test the app from a VB Classic app, I always get double/repeated text strings arriving at the client ?
-> Can't reproduce this from a telnet client.
'*** form Code ***
For the most part it works.
Issues:
1. You have to pass a reference to the listbox (or control you want to update) to your new threads.
2. If you try to broadcast a banner on the new connection, the app becomes un-stable. (event just the first one!?)
3. when I test the app from a VB Classic app, I always get double/repeated text strings arriving at the client ?
-> Can't reproduce this from a telnet client.
'*** form Code ***
Public Class frmTest01
Private Sub cmdStart_Click(sender As System.Object, e As System.EventArgs) Handles cmdStart.Click
bListening01 = True
StartListening01(Me.lstEvents) 'Call the listening function, and pass a reference to the listbox "lstEvents"
End Sub
Public Function ListEvent00(ByVal Msg As String) As Integer
ListEvent01(Me.lstEvents, Msg)
End Function
Public Delegate Function DelListEvent(ByVal LB As ListBox, ByVal Msg As String) As Integer
Public Function ListEvent01(ByVal msg As String) As Integer
ListEvent01(lstEvents, msg)
End Function
Public Function ListEvent01(ByVal LB As ListBox, ByVal Msg As String) As Integer
Dim iRet As Integer = 0
Dim sDS As String = ""
Dim sMsg As String = ""
Dim sMsg_p As String = ""
Dim iPos As Integer = -1
'If lstEvents.InvokeRequired Then
If LB.InvokeRequired Then
'iRet = lstEvents.Invoke(New MethodInvoker(AddressOf ListEvent))
Dim d As New DelListEvent(AddressOf ListEvent01)
'lstEvents.BeginInvoke(New DelListEvent(AddressOf ListEvent), Msg)
LB.BeginInvoke(d, New Object() {LB, Msg})
Else
sDS = Now.ToString("yy/MM/dd HH:mm:ss.fff")
sMsg = sDS & " > " & Msg
iPos = sMsg.IndexOf("~")
If iPos > -1 Then
Do Until iPos = -1
sMsg_p = sMsg.Substring(0, iPos)
LB.Items.Add(sMsg_p)
sMsg = sMsg.Substring(iPos + 1)
iPos = sMsg.IndexOf("~")
Loop
LB.Items.Add(sMsg)
Else
LB.Items.Add(sMsg)
End If
'Select the last item.
LB.SelectedIndex = LB.Items.Count - 1
Do Until LB.Items.Count < 11
LB.Items.RemoveAt(0)
Application.DoEvents()
Loop
Application.DoEvents()
End If
End Function
Private Sub frmTest01_FormClosing(sender As Object, e As System.Windows.Forms.FormClosingEventArgs) Handles Me.FormClosing
bListening01 = False
End Sub
End Class
'*** Module Code ***Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
'Imports System.Threading.tasks
Imports System.ComponentModel
Module modTesting01
Public sBanner As String = " Your are connected to the TCP Listener !" & vbCrLf
Public iThreadCount01 As Integer = 0
Public iConnectionCnt01 As Integer = 0
Public dServiceUpTime As Date = Now
Public dStartedListening01 As Date
Public bSendBanner As Boolean = True
Public bListening01 As Boolean = False
Public sTargetFolder As String = "C:\Projects\TestFiles\"
Public iCounter As Integer = 0
Public bLogDataRec01 As Boolean = False
Sub StartListening01(ByVal lsb As ListBox)
On Error Resume Next
Dim iPort As Integer = 9100
'Here its ok not to pass a reference to the listbox, we are not in a separate thread.
frmTest01.ListEvent01("Listener Started on Port: " & iPort.ToString)
sTargetFolder = frmListener.txtTargetFolder.Text
Dim LclIP As IPAddress = IPAddress.Parse("127.0.0.1")
Dim serverSocket As New TcpListener(LclIP, iPort)
Dim clientSocket As TcpClient
serverSocket.Start()
iCounter = 0
While (bListening01)
If serverSocket.Pending Then
iCounter += 1
clientSocket = serverSocket.AcceptTcpClient()
Application.DoEvents()
Dim client As New handleClinet01
'Start the thread and pass a reference to the listbox "lstEvents"
client.startClient(clientSocket, lsb, iCounter)
frmTest01.ListEvent01(lsb, "Connection accepted from " & clientSocket.Client.RemoteEndPoint.ToString & " #" & iCounter.ToString("#,0"))
'frmTest01.ListEvent01(lsb, "Process Thread Count: " & Process.GetCurrentProcess().Threads.Count.ToString("#,0"))
End If
Threading.Thread.Sleep(100)
Application.DoEvents()
End While
clientSocket.Close()
serverSocket.Stop()
frmTest01.ListEvent01("Listener Stopped.")
End Sub
Sub msg(ByVal mesg As String)
mesg.Trim()
Console.WriteLine(" >> " + mesg)
End Sub
Public Class handleClinet01
Dim clientSocket As TcpClient
Dim frmLsb As ListBox
Dim iCounter As Integer = 0
Public Sub startClient(ByVal inClientSocket As TcpClient, ByVal lsb As ListBox, Counter As Integer)
Me.clientSocket = inClientSocket
Me.frmLsb = lsb
Dim ctThread As Threading.Thread = New Threading.Thread(AddressOf Capture)
ctThread.Priority = ThreadPriority.Highest
ctThread.Start(lsb) 'and pass a reference to the listbox "lstEvents"
iCounter = Counter
ctThread = Nothing
MyClass.Finalize()
End Sub
Private Sub Capture(ByVal lsb As ListBox)
Dim requestCount As Integer = 0
Dim bytesEmpty(10024) As Byte
Dim bytesFrom(10024) As Byte
Dim sDataFromClient As String = ""
Dim sLineFromClient As String = ""
Dim sendBytes As [Byte]()
Dim serverResponse As String = ""
Dim rCount As String = ""
Dim iRead As Integer = -1
Dim sFileName As String = Now.ToString("yyMMdd_HHmmss_fff") & ".txt"
Dim sTargetFolder As String = "C:\Projects\TestFiles\"
Dim sPath As String = sTargetFolder & sFileName
Dim bBannerSent As Boolean = False
Dim sRemoteAddr As String = clientSocket.Client.RemoteEndPoint.ToString
Dim iByteCount As Integer = 0
Dim iThread0 As Integer = 0
Dim networkStream As NetworkStream = clientSocket.GetStream()
'Writing anything back to the Socket causes the app to crash ??
'If iCounter < 4 Then 'Only try to write the first few connections, otherwise the app will crash !
' sendBytes = Encoding.ASCII.GetBytes(sBanner)
' networkStream.Write(sendBytes, 0, sendBytes.Length)
' networkStream.Flush()
'End If
While (bListening01)
Try
requestCount = requestCount + 1
'bytesFrom = bytesEmpty
iRead = networkStream.Read(bytesFrom, 0, CInt(clientSocket.ReceiveBufferSize))
'networkStream.Flush()
sDataFromClient = System.Text.Encoding.ASCII.GetString(bytesFrom)
sDataFromClient = sDataFromClient.Substring(0, sDataFromClient.IndexOf(Chr(0)))
iByteCount += sDataFromClient.Length
sLineFromClient &= sDataFromClient
sDataFromClient = ""
If sLineFromClient.IndexOf(vbCr) > -1 Then
IO.File.AppendAllText(sPath, sLineFromClient)
sLineFromClient = ""
End If
If iRead = 0 Then 'We are disconnected.
'Now the remote connection has closed, pass a reference to the listbox "lstEvents" or you will never get your form updated.
frmTest01.ListEvent01(frmLsb, "Connection closed from ! " & sRemoteAddr)
If sLineFromClient.Length > 0 Then
IO.File.AppendAllText(sPath, sLineFromClient)
sLineFromClient = ""
End If
Exit While
End If
Catch ex As Exception
'MsgBox(ex.ToString)
End Try
End While
'Close everything up.
networkStream.Dispose()
Me.clientSocket.Close()
Me.clientSocket = Nothing
'Thread.CurrentThread.Abort()
Application.DoEvents()
'Beep()
End Sub
End Class
End Module
ASKER
Look at this link:
http://www.codeproject.com/Articles/10533/When-a-thread-will-terminate-or-dispose
That makes this comment:
Remember: You don't have to declare a delegate for the procedure you want to use for threading in VB.NET, it will be done behind the scenes in contrast to C#.
http://www.codeproject.com/Articles/10533/When-a-thread-will-terminate-or-dispose
That makes this comment:
Remember: You don't have to declare a delegate for the procedure you want to use for threading in VB.NET, it will be done behind the scenes in contrast to C#.
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
I will try to upload those files tonight when I get home.
ASKER
The "frmListener" is left over from a previous project. (just comment that line please)
The "frmTest01" is just a form with a button named "cmdStart" and a listbox named "lstEvents".
I thought I provided everything, let me know if anything is left out.
Thanks
The "frmTest01" is just a form with a button named "cmdStart" and a listbox named "lstEvents".
I thought I provided everything, let me know if anything is left out.
Thanks
ASKER
Thanks for the help, your code examples really helped.
Form1.vb -
Open in new window
Form1.Designer.vb -Open in new window
Produces the following output -Initial load:Pressing the add button (with output to console window):Pressing the remove button (with output to console window):
You could also just use an anonymous method invoker instead of the delegates:
Open in new window
Which would produce the same output as above.However, there is a problem with these methods. They are only for ListBox controls and only for specific listbox control methods. What if we could make this more generic? Well we can:
Open in new window
Which produces a different output to the console but the same results to the listbox -In most circumstances this is fine, but you have the potential for race conditions.
-saige-