I am writing a program in Visual Basic that will read text commands from the serial port that are sent using an external controller (an Arduino). However, when I try to test the code I get an error:
Cross-thread Operation Not Valid
Here is what the code looks like:
Private Sub SerialPort1_DataReceived(sender As Object, e As SerialDataReceivedEventArgs) Handles SerialPort1.DataReceived
Dim Data As String = SerialPort1.ReadExisting()
If Data = "l" Then
LeftRadio.Checked = True
ElseIf Data = "r" Then
RightRadio.Checked = True
ElseIf Data = "c" Then
CenterRadio.Checked = True
End If
End Sub
Private Sub connect_Click(sender As Object, e As EventArgs) Handles connect.Click
If Not SerialPort1.IsOpen Then
SerialPort1.PortName = "COM3"
SerialPort1.Open()
End If
End Sub
Please see Cross-thread operation not valid and Cross-thread operation not valid: Control accessed from a thread other than the thread it was created on
Short answer: you perform UI operation in not-UI (main) thread what is not permitted.
In VB.NET should be something like:
Dim check = Sub()
LeftRadio.Checked = True
End Sub
LeftRadio.Invoke(check)
I have found that the DataReceived event doesn't usually work the way you think and can block the serial port while code runs in the event.
My preference is to add a Timer to the form that runs at a reasonable speed such as 5 times per second.
In the Timer OnTick event you can test SerialPort1.BytesAvailable to see if data has arrived. Then use ReadExisting() as you have above.
The timer code will be less prone to cross thread issues.
Related
I'm new to Visual Basic and overall kind of new to coding in general.
Currently I work on a program which uses a filewatcher. But If I try this:
Public Class Form1
Private WithEvents fsw As IO.FileSystemWatcher
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load
fsw = New IO.FileSystemWatcher("PATH")
fsw.EnableRaisingEvents = True
' fsw.Filter = "*.settings"
End Sub
Private Sub GetSettingsFromFile()
Some Code
More Code
CheckBox1.Checked = True
End Sub
Private Sub fsw_Changed(sender As Object, e As FileSystemEventArgs) Handles fsw.Changed
fsw.EnableRaisingEvents = False 'this is set because the file is changed many times in rapid succesion so I need to stop the Filewatcher from going of 200x (anyone has a better idea to do this?)
Threading.Thread.Sleep(100)
GetSettingsFromFile()
fsw.EnableRaisingEvents = True 'enabling it again
End Sub
End Class
But when I do this (trying to change anyhting in the form) I get this error:
System.InvalidOperationException (WinForms.IllegalCrossThreadCall)
It wont stop the program from working, but I want to understand what is wrong here and why the debugger is throwing this at me
regards
The event is being raised on a secondary thread. Any changes to the UI must be made on the UI thread. You need to marshal a method call to the UI thread and update the UI there. Lots of information around on how to do that. Here's an example:
Private Sub UpdateCheckBox1(checked As Boolean)
If CheckBox1.InvokeRequired Then
'We are on a secondary thread so marshal a method call to the UI thread.
CheckBox1.Invoke(New Action(Of Boolean)(AddressOf UpdateCheckBox1), checked)
Else
'We are on the UI thread so update the control.
CheckBox1.Checked = checked
End If
End Sub
Now you simply call that method wherever you are and whatever thread you're on. If you're already on the UI thread then the control will just be updated. If you're on a secondary thread then the method will invoke itself a second time, this time on the UI thread, and the control will be updated in that second invocation.
Part of a program I am modifying involves communicating through a serial port using a proprietary library. Unfortunately, this library does not have the same SerialPort.DataReceived event that the System.IO.Ports namespace contains. In fact, it has no events whatsoever, however it does have two functions that can probably be used similarly:
Port.WaitForData(int time)
This function waits the given amount of time to recieve some previously specified strings over the port. It returns 1 for yes, received string, or 0 for no, did not recieve string, timed out.
Port.IsReceiveBufferEmpty()
This function returns a boolean of yes, the receive buffer is empty or no, the receive buffer contains data.
It seems to me I will have to create some thread to be continuously looping whenever the port is opened and do one of these two things:
For every loop, call WaitForData(some big number) with the specified strings it is looking for set to "", or vbCrLf, or something else that I can confirm it will recieve everytime data is sent. If it finds smoething, read it and write to a textbox. If WaitForData doesn't find anything, loop again.
For every loop, call IsReceiveBufferEmpty(), and if it isn't, read it and write to a textbox.
What the best way to go about implementing this? The first options seems potentially more efficient to me, although I know next to nothing about how these method work under the hood. Obviously I want to keep my form responsive when doing this, so how should I go about continuously looping without freezing the form but being able to read any incoming data?
Thanks for your help.
Perhaps not the most elegant solution, but you could use a BackgroundWorker to do the IO. e.g. something like this pseudo-code:
Public Class MyForm
Private _port As ProprietaryIOLibrary
Private WithEvents Worker As System.ComponentModel.BackgroundWorker
Private Sub Form1_Load(sender As Object, e As EventArgs) Handles Me.Load
_port = New ProprietaryIOLibrary()
Worker = New System.ComponentModel.BackgroundWorker()
Worker.WorkerReportsProgress = True
Worker.WorkerSupportsCancellation = True
Worker.RunWorkerAsync()
End Sub
Private Sub ButtonCancel_Click(sender As Object, e As EventArgs) Handles ButtonCancel.Click
Worker.CancelAsync()
End Sub
Private Sub Worker_DoWork(sender As Object, e As DoWorkEventArgs) Handles Worker.DoWork
Do
If _port.WaitForData(1000) Then ' Adjust timeout depending on cancel responsiveness?
Dim data As String = _port.ReadDataAsString() ' ?
' Trigger the ProgressChanged event, passing the data
Worker.ReportProgress(0, data)
End If
If Worker.CancellationPending Then
Exit Do
End If
Loop
End Sub
Private Sub Worker_ProgressChanged(sender As Object, e As ProgressChangedEventArgs) Handles Worker.ProgressChanged
' Update the UI with the data received
' ProgressChanged is called on the UI thread, so no need to Invoke
Dim data As String = DirectCast(e.UserState, String)
TextBox1.Text &= data & vbCrLf
End Sub
Private Sub Worker_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles Worker.RunWorkerCompleted
TextBox1.Text &= "Complete!"
End Sub
End Class
I want to detect if a target process has ended or not. I have written the expected sequence below:
A process named TEST runs at the background.
Status.Text = "Running" to indicate process is running.
Process ends by itself.
Status.Text = "Finished" right after the process ends.
Unfortunately, the solution posted here requires to be run as administrator.
A simple polling-solution using a timer could do the work just fine.
If you use a polling solution, then of course you have to re-read the processes inside the loop or polling event.
Use the process name without .exe here.
Private timer_watcher As Timer
Private Sub Button1_Click(sender As Object, e As EventArgs) Handles Button1.Click
Me.Label1.Text = "Watching"
Me.timer_watcher = New Timer
AddHandler timer_watcher.Tick, AddressOf TimerEvent
timer_watcher.Interval = TimeSpan.FromSeconds(1).TotalMilliseconds
timer_watcher.Start()
End Sub
Public Sub TimerEvent(sender As Object, e As EventArgs)
Dim p() As Process = System.Diagnostics.Process.GetProcessesByName("processname")
If p.Length = 0 Then
timer_watcher.Stop()
Me.Label1.Text = "Stopped"
End If
End Sub
Consider using the Process.Exited event.
Team,
I have an third party application which actually is a telephonic messaging server and exchange messages between all connected clients and other servers. This messaging server keeps running for several days and even for moths. This is entirely a console application and do not have any GUI. Even to manage the internal operations of this server, there is another tool which is a console based application again. I would like to prepare a GUI to start, stop and restart this server in VB.Net 2012. I have managed to,
Create the process instance of this server
Launch the Server with appropriate parameters and keep it running. Below is some sample code from my application to launch the server,
Private Sub Server_Start_Click(sender As Object, e As EventArgs) Handles Server_Start.Click
Dim parameter, server_admin_path As String
server_admin_path = "D:\Voice_App\DataMessage\MessageServer.exe"
parameter = " -properties " & """" & " D:\Voice_App\Config\message.prop"
Dim proc = New Process()
proc.StartInfo.FileName = server_admin_path
proc.StartInfo.Arguments = parameter
' set up output redirection
proc.StartInfo.RedirectStandardOutput = True
proc.StartInfo.RedirectStandardError = True
proc.EnableRaisingEvents = True
Application.DoEvents()
proc.StartInfo.CreateNoWindow = False
proc.StartInfo.UseShellExecute = False
' see below for output handler
AddHandler proc.ErrorDataReceived, AddressOf proc_OutputDataReceived
AddHandler proc.OutputDataReceived, AddressOf proc_OutputDataReceived
proc.Start()
proc.BeginErrorReadLine()
proc.BeginOutputReadLine()
'proc.WaitForExit()
Server_Logs.Focus()
End sub
This code launches the message server very well. The message server is now started and it is producing log traces on the console after specific time of interval say 30 seconds and this will be continue till message server is not stopped by administration tool. So now what I want is to capture every single line that is being produced by my server on its console and paste that line on to the Textbox I have on my windows form.
I got below code which gives me that every line as and when produced,
Public Sub proc_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
On Error Resume Next
' output will be in string e.Data
' modify TextBox.Text here
'Server_Logs.Text = e.Data ` Does not display anything in textbox
MsgBox(e.Data) 'It works but I want output in text box field
End Sub
P.S = My application will be handling more that one such servers and I don’t want users to have every message server instance open on their taskbar as console window and they are scrolling long log traces. I searched lots of threads here but nothing worked for me in above scenario. Any help would be greatly appreciated as I have been stuck on this since very long time and this is now a showstopper!!!!
Looks like you are trying to make a call from a thread that is different from the thread the form is on. The events raised from the Process class will not be from the same thread.
Delegate Sub UpdateTextBoxDelg(text As String)
Public myDelegate As UpdateTextBoxDelg = New UpdateTextBoxDelg(AddressOf UpdateTextBox)
Public Sub UpdateTextBox(text As String)
Textbox.Text = text
End Sub
Public Sub proc_OutputDataReceived(ByVal sender As Object, ByVal e As DataReceivedEventArgs)
If Me.InvokeRequired = True Then
Me.Invoke(myDelegate, e.Data)
Else
UpdateTextBox(e.Data)
End If
End Sub
I have a background worker which calls a function within a separate class. This process may be required to be canceled at any time via a button click from the front end. I have tried using CancelAsync() but this has no effect. The cofunds_downloadfiles is the function which i am calling. How do i go about canceling the process?
TIA.
Private Sub btnProcessdld_Click(sender As System.Object, e As System.EventArgs) Handles btnProcessdld.Click
Dim cfHelper As New CoFundsHelper
If btnProcessdld.Text = "Process" Then
btnProcessdld.Text = "Cancel"
If chkDailyFiles.Checked = False And chkWeeklyFiles.Checked = False Then
MessageBox.Show("Please select which files you want to download")
Else
lblProgress.Text = "Processing...if you are downloading weekly files this may take a few minutes"
uaWaitdld.AnimationEnabled = True
uaWaitdld.AnimationSpeed = 50
uaWaitdld.MarqueeAnimationStyle = MarqueeAnimationStyle.Continuous
uaWaitdld.MarqueeMarkerWidth = 60
_backGroundWorkerdld = New BackgroundWorker
_backGroundWorkerdld.WorkerSupportsCancellation = True
_backGroundWorkerdld.RunWorkerAsync()
End If
ElseIf btnProcessdld.Text = "Cancel" Then
btnProcessdld.Text = "Process"
_backGroundWorkerdld.CancelAsync()
uaWaitdld.AnimationEnabled = False
End If
Private Sub StartProcessdld(ByVal sender As Object, _
ByVal e As System.ComponentModel.DoWorkEventArgs) Handles _backGroundWorkerdld.DoWork
Dim cfHelper As New CoFundsHelper
cfHelper.ConnString = PremiumConnectionString
Dim dateValue As String
Dim weekly As Boolean = False
Dim daily As Boolean = False
If dtePicker.Value IsNot Nothing Then
dateValue = Format(dtePicker.Value, "yyyyMMdd")
If chkWeeklyFiles.Checked = True Then
weekly = True
End If
If chkDailyFiles.Checked = True Then
daily = True
End If
cfHelper.Cofunds_DownloadFiles(dateValue, weekly, daily)
Else
Throw (New Exception("Date Field is empty"))
End If
End Sub
Basically you can do the following:
in the DoWork sub, test for cancellationpending property
if it's true then you simply do not call that function and maybe put e.Cancelled = true then check this in the RunWorkerCompleted and decide what you have to do.
if you need to cancel it, simply make a Stop() sub in your class that does exactly that - stops the procedure. Then, you simply need to invoke it like
Me.Invoke(Sub()
myClass.Stop()
End Sub)
you may need to suspend the background worker until the call from your main thread has returned. You do this using semaphores: Private chk As New Semaphore(1,1,"checking1") You put this as a global variable to both your main thread and the background worker.
in the backgroundworker_doWork you use the semaphore like chk.WaitOne() AFTER the line that need to execute.
in the method of your class, when it has finished with computing you put a.Release
The semaphore is only needed if you need to make sure you wait for a result. It kind of defeats the purpose of multithreading but you can perform other actions in the worker before waiting for the main thread (like starting another thread with something else etc).
Other than that invoking a stop method should be enough. Sorry that i haven't had time to analyze your code but i hope i put you in the right direction.
CancelAsync doesn't actually do the cancelling of the worker (is just sets CancellationPending = True) so you basically have to check the state of the BackGroundWorker in your function:
Do While Not worker.CancellationPending
'some long running process
Loop
I have found that this is not 100% reliable however, so it may be safer to use a cancelled flag of your own.