I am writing an application in VB.NET that allows users to schedule submissions (emails) to be sent at a later date. I use threads to wait until the time is right to send a particular submission, but for some reason I can't access one of the class objects from the listener threads (or something else is happening, that's what I'm trying to figure out). Here is the relevant code:
Public Class AppContext
Inherits ApplicationContext
Private submsnMngr As SubmissionManager
Public Sub New()
submsnMgr = New SubmissionManager()
menuAddEdit = New ToolStripMenuItem("Add/Edit Submissions")
...
End Sub
Private Sub menuAddEdit_Click(ByVal sender As Object, ByVal e As System.EventArgs)
Handles menuAddEdit.Click
' The user clicking this tray button is the ONLY way that the form can be shown
submsnMngr.ShowWelcome()
End Sub
...
End Class
Class SubmissionManager
Public currentSubmissions As SubmissionList
Public WelcomeForm As Welcome
Public Sub ShowWelcome()
If WelcomeForm Is Nothing Then
' Welcome is the form that needs to be refreshed down in the MailSender subroutine
WelcomeForm = New Welcome(Me)
End If
WelcomeForm.Show()
End Sub
Public Sub CheckDates()
For Each submsn In currentSubmissions.Submissions
SyncLock submsn
If Today.Date >= submsn.EffDate.AddDays(-90).Date And Not submsn.Sent90 And Not submsn.Denied90 And submsn.Thread Is Nothing Then
submsn.Send(1)
submsn.Sent90 = True
currentSubmissions.Save()
ElseIf Today.Date = submsn.EffDate.AddDays(-91).Date And submsn.Thread Is Nothing Then
Dim thd As New Thread(AddressOf MailSender)
thd.IsBackground = True
submsn.Thread = thd
Dim args As New ThreadArgs(submsn.Insured, 1)
thd.Start(args)
End If
If Today.Date >= submsn.EffDate.AddDays(-60).Date And submsn.Thread Is Nothing Then
submsn.Send(2)
currentSubmissions.RemoveSubmission(submsn)
If WelcomeForm IsNot Nothing Then
WelcomeForm.RefreshSubmissions()
End If
ElseIf Today.Date = submsn.EffDate.AddDays(-61).Date And submsn.Thread Is Nothing Then
Dim thd As New Thread(AddressOf MailSender)
thd.IsBackground = True
submsn.Thread = thd
Dim args As New ThreadArgs(submsn.Insured, 2)
thd.Start(args)
End If
End SyncLock
Next
End Sub
Private Sub DateListener()
Do
CheckDates()
Thread.Sleep(3600000)
Loop
End Sub
Private Sub MailSender(args As ThreadArgs)
Dim wait As New TimeSpan(14 - DateTime.Now.Hour, 23 - DateTime.Now.Minute, 0)
Thread.Sleep(wait.TotalMilliseconds)
Dim submsn As Submission = currentSubmissions.GetSubmission(args.insured)
SyncLock submsn
submsn.Send(args.mode)
If args.mode = 1 Then
submsn.Sent90 = True
submsn.Thread = Nothing
currentSubmissions.Save()
Else
currentSubmissions.RemoveSubmission(submsn)
End If
End SyncLock
If WelcomeForm IsNot Nothing Then
' Here is the issue, this code is not being run, even though WelcomeForm is set
' in New() above
WelcomeForm.RefreshSubmissions()
End If
End Sub
End Class
Paying special attention to the few comment lines in the code above, why is WelcomeForm Nothing when I clearly set it to reference the form created in the New() subroutine? I tried alternatively sending the reference to the MailSender thread as an argument, but the same thing happened. Note that I need the If statement there because the user may have closed the form before the thread gets to that point. But it is essential that RefreshSubmissions() be called on it if it is still open.
Sorry guys, realized my thread was being aborted elsewhere in my application's code. No problems with the actual code I posted above.
Related
In order to keep responsiveness in the UI, I use a separate thread to execute various process, for example some FTP download.
Private Sub Button11_Click(sender As Object, e As EventArgs) Handles Button11.Click
Dim ThreadResync As System.Threading.Thread
ThreadResync = New System.Threading.Thread(AddressOf Bodacc_ResyncFTP)
ThreadResync.Start()
End Sub
Sub Bodacc_ResyncFTP()
Dim MyBodacc As bodacc_data = New bodacc_data
MyBodacc.Label_Status = Form1.Label1
MyBodacc.ResyncFTP()
End Sub
A way to update the UI with threading is the Delegate thingy, so in the bodacc_data I had to
Public Class bodacc_data
......
Delegate Sub UpdateLabelDelg(text As String, ThisLabel As Label)
Public Delegate_label As UpdateLabelDelg = New UpdateLabelDelg(AddressOf set_label)
Public Label_Status = Label
......
Sub set_label(stext As String, ThisLabel As Label)
ThisLabel.Text = stext
End Sub
.....
Sub ResyncFTP()
//says hello
If Label_Status.InvokeRequired = True Then
Label_Status.Invoke(Delegate_label, New Object() {"Working...", Label_Status})
Else
Label_Status.Text = "Working..."
End If
//do stuff
End Sub
End Class
It works like a charm. But I have many class doing more or less the same (disk update, database update, FTP update) and having to copy/past all the delegate / external label declaration / mini sub / invoke sound silly.
So I created a class to handle those UI update / delegate in order to have a quick access
Public Class Form_UI
Delegate Sub UpdateLabelDelg(text As String, ThisLabel As Label)
Public Delegate_label As UpdateLabelDelg = New UpdateLabelDelg(AddressOf set_label)
Private Labels(2) As Label
Sub New()
Labels(0) = Form1.Label1
Labels(1) = Form1.Label2
Labels(2) = Form1.Label3
End Sub
Sub set_label(stext As String, ThisLabel As Label)
ThisLabel.Text = stext
End Sub
Public Sub ChangeLabel(ByVal LabelNum As Integer, nText As String)
LabelNum = LabelNum - 1
If Labels(LabelNum).InvokeRequired Then
Labels(LabelNum).Invoke(Delegate_label, New Object() {nText, Labels(LabelNum)})
Else
Labels(LabelNum).Text = nText
Labels(LabelNum).Update()
End If
End Sub
End Class
So, now in the revamped bodacc_data and all others processing class I have only :
Public Class bodacc_data
......
Private MyUI as Form_UI
.....
Sub New()
MyUI = New Form_UI()
End Sub
Sub ResyncFTP()
//says hello
MyUI.ChangeLabel(1, "Working...")
//do stuff
End Sub
End Class
Question Why is MyUI.ChangeLabel not updating when the ResyncFTP is called in a thread, but works if called in the main thread (As in the code sample below)
Private Sub Button11_Click(sender As Object, e As EventArgs) Handles Button11.Click
Dim MyBodacc As bodacc_data = New bodacc_data
MyBodacc.ResyncFTP()
End Sub
Note that there is no error thrown. The notable weirdness is that <Form_UI>.ChangeLabel() never goes the .Invoke route but the normal update route. I strongly suspect a scope issue or insight issue.
When you create a windows forms app you set up a UI thread that is meant to be the owner of all the UI. The UI thread contains the message pump that is used to update all of the UI.
But what you're doing in Button11_Click is creating a new thread that goes and calls Dim MyBodacc As bodacc_data = New bodacc_data which, in turn, calls MyUI = New Form_UI().
You are creating a form on a non-UI thread. There is no message pump and therefore the UI doesn't update.
I'm looking at a console app for vb.net. I'm trying to get a worker thread to raise an event to the main thread to display data on the screen (the word "HIT" everytime the worker thread completes a cycle). My code is below.
I'm not sure why but the main thread's Private Sub CounterClass_GivingUpdate() Handles _counter.AboutToDistributeNewupdate isn't executing.
Imports System.Threading
Module Module1
Private WithEvents _counter As CounterClass
Private trd As Thread
Sub Main()
While True
Dim s As String = Console.ReadLine()
Dim started As Boolean
Select Case s
Case "status"
WriteStatusToConsole("You typed status")
Case "startcounter"
If started = False Then
starttheThread()
started = True
WriteStatusToConsole("You Have Started The Timer")
Else
WriteStatusToConsole("YOU HAVE ALREADY STARTED THE TIMER!!!")
End If
End Select
End While
End Sub
Private Sub CounterClass_GivingUpdate() Handles _counter.AboutToDistributeNewupdate
WriteStatusToConsole("Hit")
End Sub
Private Sub starttheThread()
Dim c As New CounterClass
trd = New Thread(AddressOf c.startProcess)
trd.Start()
End Sub
Sub WriteStatusToConsole(ByVal stringToDisplay As String)
Console.WriteLine(stringToDisplay)
End Sub
End Module
Public Class CounterClass
Public Event AboutToDistributeNewupdate()
Public Sub sendStatusUpdateEvent(ByVal updatestatus As String)
RaiseEvent AboutToDistributeNewupdate()
End Sub
Public Sub startProcess()
Dim i As Int64
Do
Thread.Sleep(1000)
i = i + 1
sendStatusUpdateEvent(i.ToString)
Loop
End Sub
End Class
Your CounterClass_GivingUpdate() only handles the _counter variable's event (the variable that you do not use!). Every time you declare a new CounterClass it has its own instance of the event that it raises.
You know have two options:
Option 1
Subscribe to the event for each new CounterClass instance you create. Meaning you must use the AddHandler statement every time you create a new instance of your class:
Private Sub starttheThread()
Dim c As New CounterClass
AddHandler c.AboutToDistributeNewupdate, AddressOf CounterClass_GivingUpdate
trd = New Thread(AddressOf c.startProcess)
trd.Start()
End Sub
Option 2
Mark the event as Shared to make it available without needing to create an instance of the class. For this you must also change how you subscribe to the event, by subscribing to it in your method Main():
Sub Main()
AddHandler CounterClass.AboutToDistributeNewupdate, AddressOf CounterClass_GivingUpdate
...the rest of your code...
End Sub
Private Sub CounterClass_GivingUpdate() 'No "Handles"-statement here.
WriteStatusToConsole("Hit")
End Sub
Public Class CounterClass
Public Shared Event AboutToDistributeNewupdate() 'Added the "Shared" keyword.
...the rest of your code...
End Class
I am new to vb.net multithreading, and I am facing a issue.
For threading I am using system.threading.thread function.
I know I can stop a thread using thread.abort call. But that's not a good choice (I have searched a bit and came to know that). This also will not work because my running threads needs some cleanup before cancellation.
So, I'm trying to do some kind of action that set a variable to UI thread like
dim bolStop as Boolean
So, if my UI thread set that Boolean value as true then all running thread can access/notice that change and can start cleanup process before stop/cancel the thread itself.
hope that makes sense...
best regards
Edit:1 (Source Is Pasted Here as Suggested By #DrunkenCodeMonkey
This is the code for Main Form (Form1.vb) (I think i need something here and also on the thread module (class) too) so, below i will paste module code too
Public Class Form1
Private objThreadList As ArrayList
Dim bolStop As Boolean
Private Sub cmd1_Click(sender As System.Object, e As System.EventArgs) Handles cmd1.Click
Dim intTLoop As Integer
sPrepareThreading()
For intTLoop = 1 To 5
sSendThread("", "Zakir", "Biplob")
Next
'sFinishThreading()
MessageBox.Show("Done!")
End Sub
Sub sPrepareThreading()
Dim intTLoop As Integer
objThreadList = New ArrayList
For intTLoop = 1 To 3
objThreadList.Add(Nothing)
Next intTLoop
End Sub
Sub sSendThread(strDraft As String, ByVal strTitle As String, ByVal strSubHeader As String)
Dim bolFalied As Boolean
Dim intTLoop As Integer
Dim objItem As System.Windows.Forms.ListViewItem
Dim objTData As objThreadCrossData
Dim objThreadClass As clsThread
Dim objNewThread As System.Threading.Thread
Zakir_RecheckThread:
My.Application.DoEvents()
If bolStop = True Then
Exit Sub
End If
bolFalied = True
For intTLoop = 1 To 3
bolFalied = True
If objThreadList(intTLoop - 1) Is Nothing Then
bolFalied = False
Else
If CType(objThreadList(intTLoop - 1), System.Threading.Thread).IsAlive = False Then
bolFalied = False
Else
bolFalied = True
End If
End If
If bolFalied = False Then
objTData = New objThreadCrossData
objTData.ID = ListView1.Items.Count
objTData.bolStop = False
objTData.strTime = DateTime.Now.ToString
objItem = ListView1.Items.Add(strTitle)
objItem.SubItems.Add(strSubHeader)
objItem = Nothing
objThreadClass = New clsThread(objTData, "", Me)
objNewThread = New System.Threading.Thread(AddressOf objThreadClass.StartThread)
objNewThread.IsBackground = True
objNewThread.Start()
objThreadList(intTLoop - 1) = objNewThread
Exit For
End If
Next
If bolFalied = True Then
GoTo Zakir_RecheckThread
Exit Sub
End If
End Sub
Public Sub ReceiveThreadMessage(ByVal objCrosData As Object)
Dim objTDataV1 As objThreadCrossData
objTDataV1 = CType(objCrosData, objThreadCrossData)
ListView1.Items(objTDataV1.ID).SubItems(1).Text = objTDataV1.strProgress
End Sub
Private Sub cmd2_Click(sender As System.Object, e As System.EventArgs) Handles cmd2.Click
REM This is where i like to stop the running thred either by setting bolStop = True and all running thead check that variable.
REM or by chate the thread (Thread Data Object Property naemd "bolStop" value"
REM please help me out...
End Sub
End Class
Code For Thread Module (clsThread.vb):
Public Class clsThread
Private m_Counter As Integer = 0
Private m_MainWindow As Form
Dim m_ThreadCrorssData As objThreadCrossData
Private Delegate Sub NotifyMainWindow(ByVal objCrosData As objThreadCrossData)
'We need an object of this deletegate
Private m_NotifyMainWindow As NotifyMainWindow
Public Sub New(ByVal objCrossData As Object, ByVal strXML As String, ByRef MainWindow As Form1)
m_MainWindow = MainWindow
m_ThreadCrorssData = CType(objCrossData, objThreadCrossData)
'We need to point our delegate to the Method, which we want to call from this thread
m_NotifyMainWindow = AddressOf MainWindow.ReceiveThreadMessage
End Sub
Public Sub StartThread()
While m_Counter < 100
m_Counter = m_Counter + 1
rem for example here i like to check for the value that whether user asked to stop
m_ThreadCrorssData.strProgress = CStr(m_Counter)
m_MainWindow.Invoke(m_NotifyMainWindow, m_ThreadCrorssData)
rem for example also here i like to check for the value that whether user asked to stop
'wait for some time before continuing loop
System.Threading.Thread.Sleep(1000)
End While
End Sub
End Class
Here is the Code For Module (Thread Object Data)
Module mdlCMM
Public Class objThreadCrossData
Property strProgress As String
Property bolStop As Boolean = False
Property strTime As String
Property ID As Integer
Property intStatusNum As Integer
Property strStatusValue As String
End Class
End Module
I haven't really done much with threads before and I'm having a problem updating a label and closing a form.
When debugging, the CloseDialog() sub is definitely running in the main thread so I don't understand why it's not closing. There are no loops or anything else running on the form to keep it open. I'm also having a problem updating the text on a label with information passed from another thread in real time.
The AddOUToTreeView sub gets invoked and works fine, but the subs from frmStatus never do anything.
frmMain:
Private WithEvents bkg As New ADSearcher
Private Sub startSearch_Click(ByVal sender As Object, ByVal e As EventArgs) Handles startSearch.Click
With bkg
.RootPath = "LDAP://domain.com"
.FilterString = "(objectCategory=organizationalUnit)"
If Not integratedAuth Then
.UserID = "user"
.Password = "pass"
End If
.PageSize = 5
.PropertiesToLoad = New String() {"cn", "name", "distinguishedName", "objectCategory"}
Dim search As New Threading.Thread(AddressOf .StartSearch)
search.Start()
Dim statusDialog As frmStatus = New frmStatus
statusDialog.Show() 'I want to use ShowDialog() but removed it when trouble shooting
End With
End Sub
Private Delegate Sub displayStatus(ByVal entriesFound As Integer)
Private Sub bkg_ResultFound(ByVal ousFound As Integer) Handles bkg.ResultFound
Dim display As New displayStatus(AddressOf frmStatus.UpdateOUSearcherStatus)
Me.Invoke(display, New Object() {ousFound})
End Sub
Private Delegate Sub displayResult(ByVal node As TreeNode)
Private Delegate Sub closeStatusDialog()
Private Sub bkg_SearchCompleted(ByVal ouNodes As TreeNode) Handles bkg.SearchCompleted
Dim display As New displayResult(AddressOf AddOUToTreeView)
Me.Invoke(display, New Object() {ouNodes})
Dim closeStatus As New closeStatusDialog(AddressOf frmStatus.CloseDialog)
Me.Invoke(closeStatus)
End Sub
Private Sub AddOUToTreeView(ByVal node As TreeNode)
tvOU.Nodes.Add(node)
tvOU.TopNode.Expand()
End Sub
frmStatus (Both of these functions do nothing):
Public Sub CloseDialog()
'Me.DialogResult = Windows.Forms.DialogResult.OK
Me.Close()
'Me.Dispose()
End Sub
Public Sub UpdateOUSearcherStatus(ByVal entriesFound As Integer)
'lblOUsFound.Text = Format("{0} Organizational Units Found", ousFound)
lblOUsFound.Text = entriesFound.ToString
End Sub
In my ADSearcher class I have:
Public Event ResultFound(ByVal ousFound As Integer)
Public Event SearchCompleted(ByVal ouNodes As TreeNode)
and raise the events with:
RaiseEvent ResultFound(resultCount)
'and
RaiseEvent SearchCompleted(rootNode)
I apologize in advance if my question is too long-winded. I looked at the question “How to update data in GUI with messages that are being received by a thread of another class?” and it is very close to what I am trying to do but the answer was not detailed enough to be helpful.
I have converted a VB6 app to VB.NET (VS2013). The main function of the app is to send queries to a Linux server and display the results on the calling form. Since the WinSock control no longer exists, I’ve created a class to handle the functions associated with the TcpClient class. I can successfully connect to the server and send and receive data.
The problem is that I have multiple forms that use this class to send query messages to the server. The server responds with data to be displayed on the calling form. When I try to update a control on a form, I get the error "Cross-thread operation not valid: Control x accessed from a thread other than the thread it was created on." I know I’m supposed to use Control.InvokeRequired along with Control.Invoke in order to update controls on the Main/UI thread, but I can’t find a good, complete example in VB. Also, I have over 50 forms with a variety of controls on each form, I really don’t want to write a delegate handler for each control. I should also mention that the concept of threads and delegates is very new to me. I have been reading everything I can find on this subject for the past week or two, but I’m still stuck!
Is there some way to just switch back to the Main Thread? If not, is there a way I can use Control.Invoke just once to cover a multitude of controls?
I tried starting a thread just after connecting before I start sending and receiving data, but netStream.BeginRead starts its own thread once the callback function fires. I also tried using Read instead of BeginRead. It did not work well if there was a large amount of data in the response, BeginRead handled things better. I feel like Dorothy stuck in Oz, I just want to get home to the main thread!
Thanks in advance for any help you can provide.
Option Explicit On
Imports System.Net
Imports System.Net.Sockets
Imports System.Text
Imports System.Threading
Friend Class ATISTcpClient
Public Event Receive(ByVal data As String)
Private Shared WithEvents oRlogin As TcpClient
Private netStream As NetworkStream
Private BUFFER_SIZE As Integer = 8192
Private DataBuffer(BUFFER_SIZE) As Byte
Public Sub Connect()
Try
oRlogin = New Net.Sockets.TcpClient
Dim localIP As IPAddress = IPAddress.Parse(myIPAddress)
Dim localPrt As Int16 = myLocalPort
Dim ipLocalEndPoint As New IPEndPoint(localIP, localPrt)
oRlogin = New TcpClient(ipLocalEndPoint)
oRlogin.NoDelay = True
oRlogin.Connect(RemoteHost, RemotePort)
Catch e As ArgumentNullException
Debug.Print("ArgumentNullException: {0}", e)
Catch e As Net.Sockets.SocketException
Debug.Print("SocketException: {0}", e)
End Try
If oRlogin.Connected() Then
netStream = oRlogin.GetStream
If netStream.CanRead Then
netStream.BeginRead(DataBuffer, 0, BUFFER_SIZE, _
AddressOf DataArrival, DataBuffer)
End If
Send(vbNullChar)
Send(User & vbNullChar)
Send(User & vbNullChar)
Send(Term & vbNullChar)
End If
End Sub
Public Sub Send(newData As String)
On Error GoTo send_err
If netStream.CanWrite Then
Dim sendBytes As [Byte]() = Encoding.UTF8.GetBytes(newData)
netStream.Write(sendBytes, 0, sendBytes.Length)
End If
Exit Sub
send_err:
Debug.Print("Error in Send: " & Err.Number & " " & Err.Description)
End Sub
Private Sub DataArrival(ByVal dr As IAsyncResult)
'This is where it switches to a WorkerThread. It never switches back!
On Error GoTo dataArrival_err
Dim myReadBuffer(BUFFER_SIZE) As Byte
Dim myData As String = ""
Dim numberOfBytesRead As Integer = 0
numberOfBytesRead = netStream.EndRead(dr)
myReadBuffer = DataBuffer
myData = myData & Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)
Do While netStream.DataAvailable
numberOfBytesRead = netStream.Read(myReadBuffer, 0, myReadBuffer.Length)
myData = myData & Encoding.ASCII.GetString(myReadBuffer, 0, numberOfBytesRead)
Loop
'Send data back to calling form
RaiseEvent Receive(myData)
'Start reading again in case we don‘t have the entire response yet
If netStream.CanRead Then
netStream.BeginRead(DataBuffer, 0,BUFFER_SIZE,AddressOf DataArrival,DataBuffer)
End If
Exit Sub
dataArrival_err:
Debug.Print("Error in DataArrival: " & err.Number & err.Description)
End Sub
Instead of using delegates one could use anonymous methods.
Singleline:
uicontrol.Window.Invoke(Sub() ...)
Multiline:
uicontrol.Window.Invoke(
Sub()
...
End Sub
)
If you don't want to pass an UI control every time you need to invoke, create a custom application startup object.
Friend NotInheritable Class Program
Private Sub New()
End Sub
Public Shared ReadOnly Property Window() As Form
Get
Return Program.m_window
End Get
End Property
<STAThread()> _
Friend Shared Sub Main()
Application.EnableVisualStyles()
Application.SetCompatibleTextRenderingDefault(False)
Dim window As New Form1()
Program.m_window = window
Application.Run(window)
End Sub
Private Shared m_window As Form
End Class
Now, you'll always have access to the main form of the UI thread.
Friend Class Test
Public Event Message(text As String)
Public Sub Run()
Program.Window.Invoke(Sub() RaiseEvent Message("Hello!"))
End Sub
End Class
In the following sample code, notice that the Asynchronous - Unsafe run will throw a Cross-thread exception.
Imports System.Threading
Imports System.Threading.Tasks
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.cbOptions = New ComboBox() With {.TabIndex = 0, .Dock = DockStyle.Top, .DropDownStyle = ComboBoxStyle.DropDownList} : Me.cbOptions.Items.AddRange({"Asynchronous", "Synchronous"}) : Me.cbOptions.SelectedItem = "Asynchronous"
Me.btnRunSafe = New Button() With {.TabIndex = 1, .Dock = DockStyle.Top, .Text = "Run safe!", .Height = 30}
Me.btnRunUnsafe = New Button() With {.TabIndex = 2, .Dock = DockStyle.Top, .Text = "Run unsafe!", .Height = 30}
Me.tbOutput = New RichTextBox() With {.TabIndex = 3, .Dock = DockStyle.Fill}
Me.Controls.AddRange({Me.tbOutput, Me.btnRunUnsafe, Me.btnRunSafe, Me.cbOptions})
Me.testInstance = New Test()
End Sub
Private Sub _ButtonRunSafeClicked(s As Object, e As EventArgs) Handles btnRunSafe.Click
Dim mode As String = CStr(Me.cbOptions.SelectedItem)
If (mode = "Synchronous") Then
Me.testInstance.RunSafe(mode)
Else 'If (mode = "Asynchronous") Then
Task.Factory.StartNew(Sub() Me.testInstance.RunSafe(mode))
End If
End Sub
Private Sub _ButtonRunUnsafeClicked(s As Object, e As EventArgs) Handles btnRunUnsafe.Click
Dim mode As String = CStr(Me.cbOptions.SelectedItem)
If (mode = "Synchronous") Then
Me.testInstance.RunUnsafe(mode)
Else 'If (mode = "Asynchronous") Then
Task.Factory.StartNew(Sub() Me.testInstance.RunUnsafe(mode))
End If
End Sub
Private Sub TestMessageReceived(text As String) Handles testInstance.Message
Me.tbOutput.Text = (text & Environment.NewLine & Me.tbOutput.Text)
End Sub
Private WithEvents btnRunSafe As Button
Private WithEvents btnRunUnsafe As Button
Private WithEvents tbOutput As RichTextBox
Private WithEvents cbOptions As ComboBox
Private WithEvents testInstance As Test
Friend Class Test
Public Event Message(text As String)
Public Sub RunSafe(mode As String)
'Do some work:
Thread.Sleep(2000)
'Notify any listeners:
Program.Window.Invoke(Sub() RaiseEvent Message(String.Format("Safe ({0}) # {1}", mode, Date.Now)))
End Sub
Public Sub RunUnsafe(mode As String)
'Do some work:
Thread.Sleep(2000)
'Notify any listeners:
RaiseEvent Message(String.Format("Unsafe ({0}) # {1}", mode, Date.Now))
End Sub
End Class
End Class
Thank you to those who took the time to make suggestions. I found a solution. Though it may not be the preferred solution, it works beautifully. I simply added MSWINSCK.OCX to my toolbar, and use it as a COM/ActiveX component. The AxMSWinsockLib.AxWinsock control includes a DataArrival event, and it stays in the Main thread when the data arrives.
The most interesting thing is, if you right click on AxMSWinsockLib.DMSWinsockControlEvents_DataArrivalEvent and choose Go To Definition, the object browser shows the functions and delegate subs to handle the asynchronous read and the necessary delegates to handle BeginInvoke, EndInvoke, etc. It appears MicroSoft has already done the hard stuff that I did not have the time or experience to figure out on my own!