Solved

Update listbox from another thread

Posted on 2016-08-18
29
180 Views
Last Modified: 2016-09-04
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

Open in new window

0
Comment
Question by:sidwelle
[X]
Welcome to Experts Exchange

Add your voice to the tech community where 5M+ people just like you are talking about what matters.

  • Help others & share knowledge
  • Earn cash & points
  • Learn & ask questions
  • 17
  • 10
  • 2
29 Comments
 
LVL 33

Expert Comment

by:it_saige
ID: 41761669
Invoke required would only be called if the list was in fact updated from a separate thread; e.g. -

Form1.vb -
Imports System.Threading.Tasks

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))
										  AddItem(ListBox1, [Item])
									  Next
								  End Sub)
			ElseIf btn.Equals(Button2) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
										  RemoveItem(ListBox1, [Item])
									  Next
								  End Sub)
			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 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

Open in new window

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

Open in new window

Produces the following output -

Initial load:Capture.JPGPressing the add button (with output to console window):Capture.JPGPressing the remove button (with output to console window):Capture.JPG
You could also just use an anonymous method invoker instead of the delegates:
Imports System.Threading.Tasks

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))
										  AddItem(ListBox1, [Item])
									  Next
								  End Sub)
			ElseIf btn.Equals(Button2) Then
				Task.Factory.StartNew(Sub()
									  For Each [Item] In ListBox1.Items.Cast(Of Object).Reverse()
										  RemoveItem(ListBox1, [Item])
									  Next
								  End Sub)
			End If
		End If
	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 MethodInvoker(Sub()
									   AddItem(lb, message)
								   End Sub))
		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 MethodInvoker(Sub()
									   RemoveItem(lb, item)
								   End Sub))
		Else
			Console.WriteLine("Removing {0}; from Listbox", item)
							 lb.Items.Remove(item)
		End If
	End Sub
End Class

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:
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.HandleInvokeRequired(Sub(lb) lb.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.HandleInvokeRequired(Sub(lb) lb.Items.Remove([Item]))
									  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

Open in new window

Which produces a different output to the console but the same results to the listbox -Capture.JPGCapture.JPG
In most circumstances this is fine, but you have the potential for race conditions.

-saige-
0
 

Author Comment

by:sidwelle
ID: 41761712
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
0
 
LVL 33

Expert Comment

by:it_saige
ID: 41761715
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-
0
Salesforce Has Never Been Easier

Improve and reinforce salesforce training & adoption using WalkMe's digital adoption platform. Start saving on costly employee training by creating fast intuitive Walk-Thrus for Salesforce. Claim your Free Account Now

 

Author Comment

by:sidwelle
ID: 41761723
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 ?
0
 

Author Comment

by:sidwelle
ID: 41761854
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."}"
0
 
LVL 1

Expert Comment

by:Mlungisi Ndlela
ID: 41762058
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

Open in new window

0
 

Author Comment

by:sidwelle
ID: 41762325
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 ?
0
 
LVL 1

Expert Comment

by:Mlungisi Ndlela
ID: 41762433
Why did you do this?
Public Delegate Function DelListEvent(ByVal Msg As String) As Integer

    Public Function ListEvent(ByVal Msg As String) As Integer

Open in new window

0
 

Author Comment

by:sidwelle
ID: 41762436
I used a delegate to make the call thread safe ?
0
 
LVL 33

Expert Comment

by:it_saige
ID: 41762801
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-
0
 

Author Comment

by:sidwelle
ID: 41762826
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 ?
0
 

Author Comment

by:sidwelle
ID: 41762864
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 ?
0
 
LVL 33

Expert Comment

by:it_saige
ID: 41762889
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 -
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

Open in new window

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

Open in new window

InvalidOperationException caused by an invalid Cross-thread operation -Capture.JPG
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

Open in new window


Now gives us the expected output (notice without even using a delegate).

-saige-
0
 

Author Comment

by:sidwelle
ID: 41764566
it_saige:  Do you recommend the following statement recommended my MCI on this question:

Control.CheckForIllegalCrossThreadCalls = 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 ?
0
 
LVL 33

Expert Comment

by:it_saige
ID: 41765639
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 -
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

Open in new window

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

Open in new window

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 -Capture.JPGSecond run -Capture.JPGThird run -Capture.JPGAs 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

Open in new window

First run -Capture.JPGSecond run -Capture.JPGThird run -Capture.JPGConsistency 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

Open in new window

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

Open in new window

-saige-
0
 

Author Comment

by:sidwelle
ID: 41765686
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 ?
0
 
LVL 33

Expert Comment

by:it_saige
ID: 41765704
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-
0
 

Author Comment

by:sidwelle
ID: 41765714
Can I get it done with using vb 8 using .Net 3.5 Using Delegates ?
That's currently what we have.
0
 
LVL 33

Expert Comment

by:it_saige
ID: 41765721
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 -
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

Open in new window


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

Open in new window


-saige-
0
 

Author Comment

by:sidwelle
ID: 41765787
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 ?
0
 
LVL 33

Expert Comment

by:it_saige
ID: 41765802
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. -
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

Open in new window


-saige-
0
 

Author Comment

by:sidwelle
ID: 41769302
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.
0
 
LVL 33

Expert Comment

by:it_saige
ID: 41769318
Thanks for the update.  No worries, been busy on this side of the internet as well.  ;)

-saige-
0
 

Author Comment

by:sidwelle
ID: 41775423
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 ***
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

Open in new window

'*** 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

Open in new window

0
 

Author Comment

by:sidwelle
ID: 41775527
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#.
0
 
LVL 33

Accepted Solution

by:
it_saige earned 500 total points
ID: 41775638
First, why do you think you do not have to pass a reference to the object you want to update?  How are you supposed to know what to update if you do not tell the runtime which object you want to update?  This is why the common signature for events is:
Sub EventName(sender as Object, e As EventArgs)

Open in new window

The sender *is* the control that *invoked* the event.  The purpose of invocation is to ensure that only one thread updates the control.  That is it.  The thread tasked with the job of updating the control is the thread that created the handle to the control.

Delegates are just references to methods.  By doing this, you can send a method as a parameter to another method.  A simple example to show you what I mean:
Module FunWithDelegates
    Delegate Function FilterDelegate(ByVal p As Person) As Boolean

    Sub Main()
        Dim people As Person() = {New Person("John", 41), New Person("Jane", 69), New Person("Jake", 12), New Person("Jessie", 25)}
        DisplayPeople("Children:", people, AddressOf IsChild)
        DisplayPeople("Adults:", people, AddressOf IsAdult)
        DisplayPeople("Seniors:", people, AddressOf IsSenior)
        Console.ReadLine()
    End Sub

    Sub DisplayPeople(ByVal Title As String, ByVal People As Person(), ByVal Filter As FilterDelegate)
        Console.WriteLine(Title)

        For Each p As Person In People
            If (Filter(p)) Then
                Console.WriteLine("{0}, {1} years old", p.Name, p.Age)
            End If
        Next
        Console.WriteLine(Environment.NewLine)
    End Sub

    Function IsChild(ByVal p As Person) As Boolean
        Return p.Age <= 18
    End Function

    Function IsAdult(ByVal p As Person) As Boolean
        Return p.Age >= 18
    End Function

    Function IsSenior(ByVal p As Person) As Boolean
        Return p.Age >= 65
    End Function
End Module

Class Person
    Private _Name As String
    Private _Age As Integer

    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Public Property Age() As Integer
        Get
            Return _Age
        End Get
        Set(ByVal value As Integer)
            _Age = value
        End Set
    End Property

    Public Sub New(ByVal Name As String, ByVal Age As Integer)
        Me.Name = Name
        Me.Age = Age
    End Sub
End Class

Open in new window

Which produces the following output -Capture.PNG
Now we did not need to use a delegate, we could have just as *easily* (not really?!?) passed the method itself as one of the arguments; e.g. -
Module FunWithoutDelegates
    Interface FilterInterface
        Function Evaluate(ByVal p As Person) As Boolean
    End Interface

    Sub Main()
        Dim people As Person() = {New Person("John", 41), New Person("Jane", 69), New Person("Jake", 12), New Person("Jessie", 25)}
        DisplayPeople("Children:", people, New IsChild())
        DisplayPeople("Adults:", people, New IsAdult())
        DisplayPeople("Seniors:", people, New IsSenior())
        Console.ReadLine()
    End Sub

    Sub DisplayPeople(ByVal Title As String, ByVal People As Person(), ByVal Filter As FilterInterface)
        Console.WriteLine(Title)

        For Each p As Person In People
            If (Filter.Evaluate(p)) Then
                Console.WriteLine("{0}, {1} years old", p.Name, p.Age)
            End If
        Next
        Console.WriteLine(Environment.NewLine)
    End Sub

    Class IsChild
        Implements FilterInterface
        Public Function Evaluate(ByVal p As Person) As Boolean Implements FilterInterface.Evaluate
            Return p.Age <= 18
        End Function
    End Class

    Class IsAdult
        Implements FilterInterface
        Public Function Evaluate(ByVal p As Person) As Boolean Implements FilterInterface.Evaluate
            Return p.Age >= 18
        End Function
    End Class

    Class IsSenior
        Implements FilterInterface
        Public Function Evaluate(ByVal p As Person) As Boolean Implements FilterInterface.Evaluate
            Return p.Age >= 65
        End Function
    End Class
End Module

Class Person
    Private _Name As String
    Private _Age As Integer

    Public Property Name() As String
        Get
            Return _Name
        End Get
        Set(ByVal value As String)
            _Name = value
        End Set
    End Property
    Public Property Age() As Integer
        Get
            Return _Age
        End Get
        Set(ByVal value As Integer)
            _Age = value
        End Set
    End Property

    Public Sub New(ByVal Name As String, ByVal Age As Integer)
        Me.Name = Name
        Me.Age = Age
    End Sub
End Class

Open in new window

Which produces the exact same output as the delegate method.  Whats the difference?  A smaller code footprint for starters and an easier to follow logic flow (once you recognize the pattern).  Initially, however, the second method does lend itself to following easier because of it's verbosity.  So your link is not lying, you don't need to declare the delegate (which I also illustrated in one of my previous examples: https:/Q_28964240.html?anchor=a41775423#a41761669), you could always just use the method.

As for your example code above, I am missing the following parts, the Designer file for frmTest01 and the frmListener referenced in modTesting01.

-saige-
0
 

Author Comment

by:sidwelle
ID: 41776138
I will try to upload those files tonight when I get home.
0
 

Author Comment

by:sidwelle
ID: 41776155
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
0
 

Author Closing Comment

by:sidwelle
ID: 41784021
Thanks for the help, your code examples really helped.
0

Featured Post

SharePoint Admin?

Enable Your Employees To Focus On The Core With Intuitive Onscreen Guidance That is With You At The Moment of Need.

Question has a verified solution.

If you are experiencing a similar issue, please ask a related question

Suggested Solutions

Welcome my friends to the second instalment and follow-up to our Minify and Concatenate Your Scripts and Stylesheets (http://www.experts-exchange.com/Programming/Languages/.NET/ASP.NET/A_4334-Minify-and-Concatenate-Your-Scripts-and-Stylesheets.html)…
Parsing a CSV file is a task that we are confronted with regularly, and although there are a vast number of means to do this, as a newbie, the field can be confusing and the tools can seem complex. A simple solution to parsing a customized CSV fi…
Attackers love to prey on accounts that have privileges. Reducing privileged accounts and protecting privileged accounts therefore is paramount. Users, groups, and service accounts need to be protected to help protect the entire Active Directory …

730 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question