'System.Collections.Generic.KeyNotFoundException' at the end of Invoke - vb.net

I have just learnt how to set up a GUI in a separate thread...
Private myGui As SomeGui
Public Class myAsyncState
Public a As Boolean = True
Public b As Integer = 100
End Class
Public Sub Caller()
'
myGui = New SomeGui()
' setup
myGui.Begin()
Dim a as Boolean = False
Dim b as Integer = 1
Dim state As myAsyncState = New myAsyncState(a, b)
Dim step1 As New xDelegate(AddressOf xMethod)
Dim callBack As New AsyncCallback(AddressOf xMethod_Callback)
Dim asyncResultTest As IAsyncResult = step1.BeginInvoke(a, b, callBack, state)
End Sub
Private Delegate Sub xDelegate(Byval a as Integer, ByVal b As Boolean)
Public Sub xMethod(Byval a as Integer, ByVal b As Boolean)
End Sub
Private Sub xMethod_Callback(ByVal ia As IAsyncResult)
Dim myAsyncResult As AsyncResult = CType(ia, AsyncResult)
Dim myAsyncMethodCaller As xDelegate = CType(myAsyncResult.AsyncDelegate, xDelegate)
Dim state As myAsyncState = CType(myAsyncResult.AsyncState, myAsyncState)
myAsyncMethodCaller.EndInvoke(ia)
xMethod_Finish(state.a, state.b)
End Sub
Private Sub xMethod_Finish(ByVal a As Integer, ByVal b As Boolean)
If Me.InvokeRequired Then
Invoke(New xDelegate(AddressOf xMethod_Finish), New Object() {a, b}) ' here
' Also tried Invoke(New xDelegate(AddressOf xMethod_Finish), a, b) though the above is what I have seen in documentation
' also tried to make Dim state As myAsyncState = New myAsyncState(a, b) and use it as an argument
Else
yMethod(a, b)
myGui.Finish()
End If
End Sub
I was returning and passing values, it was all so good... and then I returned to it to test it, and got an error:
A first chance exception of type 'System.Collections.Generic.KeyNotFoundException' occurred in mscorlib.dll
A first chance exception of type 'System.Reflection.TargetInvocationException' occurred in mscorlib.dll
An unhandled exception of type 'System.Collections.Generic.KeyNotFoundException' occurred in System.Windows.Forms.dll
Additional information: The given key was not present in the dictionary.
The exception was after "xMethod" completed, on the line I show "here" - in the xMethod_Finish. It looks like there are some mismatched parameters - but I thought I had them all correct - and I put a lot of effort in understanding how to pass parameters to the delegate, so that they can also be passed to the subsequent method, after the EndInvoke of the first one (that will still be in the GUI thread).
Please help me see what I am doing wrong. Thank you.

Your invokation looks correct.
The error is likely in the yMethod or myGui.Finish() methods. Exceptions can become a little hidden when they occur in an invoked method. Check the exception's InnerException property to gain more information and the stack trace of what's causing the KeyNotFoundException.
You can set a breakpoint in the problem methods to debug the error.

Related

VB.net - How can a definition at module level throw an exception

I was beginning to think that I was getting good at VB.net, but not this one has me stumped.
Code looks something like this
Public Class MyServer
.....
Public myMQTTclient = New MqttClient("www.myserv.com")
.....
Private Sub Ruptela_Server(sender As Object, e As EventArgs) Handles
MyBase.Load
<some code>
StartMQTT()
<some more code>
MQTT_Publish(.....)
End Sub
Public Function StartMQTT()
' Establish a connection
Dim code As Byte
Try
code = myMQTTclient.Connect(MQTT_ClientID)
Catch ex As Exception
<error handling code>
End Try
Return code
End Function
Public Sub MQTT_Publish(ByVal DeviceID As String, ByVal Channel As String, ByVal ChannelType As String, ByVal Value As String, ByVal Unit As String)
Dim myTopic As String = "MyTopic"
Dim myPayload As String = "My Payload"
Dim msgId As UShort = myMQTTclient.Publish(myTopic, Encoding.UTF8.GetBytes(myPayload), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, False)
End Sub
As this stands it works 100% OK. The coding may seem a bit odd, but the intent is as follows :
a) create an object 'myMQTTclient' at module level so it has scope throughout the module
b) run StartMQTT() - It can still see the object.
c) within main program call MQTT_Publish many times - I can still see the object
Now the issue is this... it all goes well until "www.myserv.com" fails DNS, then the underlying winsock code throws an exception.
So ... I'm thinking - no problem - just wrap the declaration in a try block or check that www.myserv.com exists before launching the declaration.
Ah, but you can't put code at module level, it has to be in a sub or function.
Hmmm... now I'm stumped. There has to be a 'proper' way to do this, but I'll be darned if I can figure it out.
Anyone able to help ?
I'd follow the advice from #djv about declaring it just as you need it. To wrap that in a Try... Catch block you can do that in an Init method.
Public Class MyServer
Implements IDisposable ' As per djv recommendation which I second...
Private myMQQTclient As MqttClient
Public Sub Init()
Try
myMQQTClient = New MqttClient("<your url>")
Catch ex As Exception
' Do whatever
End Try
End Sub
' more code and implement the Dispose method...
End Class
You can then go on and implement the IDisposble interface to ensure that you release the resources.
Ok, I am not sure if I have the right library. But I found this Nuget package: OpenNETCF.MQTT which seems to have the class you are using.
I would do it this way
Public Class MyServerClass
Implements IDisposable
Public myMQTTclient As MQTTClient
'Private Sub Ruptela_Server(sender As Object, e As EventArgs) Handles MyBase.Load
' ' <some code>
' StartMQTT()
' ' <some more code>
' ' MQTT_Publish(.....)
'End Sub
Public Sub New(brokerHostName As String)
myMQTTclient = New MQTTClient(brokerHostName)
End Sub
Public Function StartMQTT()
' Establish a connection
Dim code As Byte
Try
code = myMQTTclient.Connect(MQTT_ClientID)
Catch ex As Exception
'<error handling code>
End Try
Return code
End Function
Public Sub MQTT_Publish(ByVal DeviceID As String, ByVal Channel As String, ByVal ChannelType As String, ByVal Value As String, ByVal Unit As String)
Dim myTopic As String = "MyTopic"
Dim myPayload As String = "My Payload"
Dim msgId As UShort = myMQTTclient.Publish(myTopic, Encoding.UTF8.GetBytes(myPayload), MqttMsgBase.QOS_LEVEL_EXACTLY_ONCE, False)
End Sub
#Region "IDisposable Support"
Private disposedValue As Boolean
Protected Overridable Sub Dispose(disposing As Boolean)
If Not disposedValue Then
If disposing Then
myMQTTclient.Dispose()
End If
End If
disposedValue = True
End Sub
Public Sub Dispose() Implements IDisposable.Dispose
Dispose(True)
End Sub
#End Region
End Class
And now you can see the usage when IDisposable is implemented, in a Using block:
Module Module1
Sub Main()
Using myserver As New MyServerClass("www.myserv.com")
myserver.StartMQTT()
myserver.MQTT_Publish(...)
End Using
End Sub
End Module
This makes it so your object is only in scope in the Using, and the object's Dispose method will automatically be called on End Using
I don't know what the base class was originally and why this was declared Private Sub Ruptela_Server(sender As Object, e As EventArgs) Handles MyBase.Load. It seems like it was possibly a form? You should keep your server code separate from Form code if that was the case. I suppose you could paste the Using into your form load, but then you would be blocking your UI thread. The referenced library has Async support so it might be a good idea to leverage that if coming from a UI.
I've made many assumptions, so I'll stop to let you comment and see how close relevant my answer is.

Fatal Exception During Run-Time of Deserialization Class (Binary formatted-data)

I am instantiating a deserializing class to fetch data from a binary-formatted data file. This appraoch seems to work fine most of the time, except when some of the data files get larger than 2-4 MB, and the following exception is thrown (at the code line with comments, below):
"The runtime has encountered a fatal error. The address of the error was at 0x722de24f, on thread 0x30e0. The error code is 0xc0000005. This error may be a bug in the CLR or in the unsafe or non-verifiable portions of user code. Common sources of this bug include user marshaling errors for COM-interop or PInvoke, which may corrupt the stack."
Here is the code:
<Serializable()>
Public Class deserializedata
Public Property sernumrows As Integer
Public Property sernumcols As Integer
Public Property serx As Object(,)
Public snumrows, snumcols As Integer
Public sx As Object(,)
Sub New(ByRef snumrows As Integer, ByRef snumcols As Integer, ByRef sx(,) As Object, ByVal sfilename as String)
Dim param_obj(0) As Object
param_obj(0) = sfilename
Call deser(param_obj)
End Sub
Sub deser(ByVal param_obj)
sfilename = param_obj(0)
Using stream As FileStream = File.Open(sfilename, FileMode.Open, FileAccess.Read, FileShare.Read)
Dim formatter As New BinaryFormatter()
Dim myser As Object = formatter.Deserialize(stream) 'this is where exception is being thrown
snumrows = myser.sernumrows
snumcols = myser.sernumcols
sx = myser.serx
stream.Close()
End Using
End Sub
End Class

When to expect a `Cross-thread Exception`?

I'm having a hard time trying to understand what seems to be a random throwing of cross-thread exceptions.
Examples
When invoked in a different thread, why does this work:
Dim text As String = Me.Text
While this will throw an exception:
Me.Text = "str"
What makes it even stranger is that the following do work:
Dim text As String = Me.ctl.Margin.ToString() : Me.ctl.Margin = New Padding(1, 2, 3, 4)
Dim text As String = Me.ctl.MyProp : Me.MyProp = "str"
Note
Yes, I know that I could just invoke the property like this:
Me.Invoke(Sub() Me.Text = "str")
Question
So when can I expect a cross-thread exception?
Code
This is the code i used to test the Me.Text property:
Public Class Form1
Public Sub New()
Me.InitializeComponent()
Me.ctl = New Control()
Me.ctl.Text = "test_control"
Me.Controls.Add(Me.ctl)
End Sub
Private Sub TestGet(sender As Object, e As EventArgs) Handles Button1.Click
Dim t As New Thread(AddressOf Me._Proc)
t.Start(TESTTYPE.GET)
End Sub
Private Sub TestSet(sender As Object, e As EventArgs) Handles Button2.Click
Dim t As New Thread(AddressOf Me._Proc)
t.Start(TESTTYPE.SET)
End Sub
Private Sub _Proc(tt As TESTTYPE)
Dim text As String = String.Empty
Dim [error] As Exception = Nothing
Try
If (tt = TESTTYPE.GET) Then
text = Me.ctl.Text
ElseIf (tt = TESTTYPE.SET) Then
Me.ctl.Text = "test"
End If
Catch ex As Exception
[error] = ex
End Try
Me.Invoke(Sub() Me._Completed(tt, text, [error]))
End Sub
Private Sub _Completed(tt As TESTTYPE, text As String, ByVal [error] As Exception)
If ([error] Is Nothing) Then
If (tt = TESTTYPE.GET) Then
MessageBox.Show(String.Concat("Success: '", text, "'"), tt.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Information)
ElseIf (tt = TESTTYPE.SET) Then
MessageBox.Show("Success", tt.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Information)
End If
Else
MessageBox.Show([error].Message, tt.ToString(), MessageBoxButtons.OK, MessageBoxIcon.Error)
End If
End Sub
Private ReadOnly ctl As Control
Private Enum TESTTYPE
[GET] = 0
[SET] = 1
End Enum
End Class
Edit
This will not throw an exception:
Public Event TestChanged As EventHandler
Public Property Test() As String
Get
Return Me.m_test
End Get
Set(value As String)
If (value <> Me.m_test) Then
Me.m_test = value
Me.Invalidate()
RaiseEvent TestChanged(Me, EventArgs.Empty)
End If
End Set
End Property
the main time a cross-thread exception occurs when you do something that would cause an event to fire from the non-UI thread that affects the UI thread; So reading a property can be fine, but writing the property of a control would cause it to repaint (at the very least), hence the exception.
Of course, other vendors may have used the exception for other scenarios where it is not safe to access from a different thread
So when can I expect a cross-thread exception?
Well, GUI in .Net are created in STA which means that only the thread that create the control can update it this has to do with Thread-safe concept. for this reasons when you start another thread and try to access the control which is owned by the main thread you will get an invalidOperationException
So when can I expect a cross-thread exception?
Really simple, when you access some function or property of a control, from a thread which do not have right to access it.
For example, in Window form application when you try to access the button placed on form from a non-ui thread, i.e. not the main thread, (and you have not set any flags manually to allow cross-thread operation)
EDIT As per comment, how can I know I can/can not access a getter/ setter of a property. Where are the access rights defined? you can always be on safe side by querying the control's InvokeRequired property in Windows
Okay, so I did some research myself, and it turns out that the exception originates in the .Handle property of the Control.
<Browsable(False), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden), DispId(-515), SRDescription("ControlHandleDescr")> _
Public ReadOnly Property Handle As IntPtr
Get
If ((Control.checkForIllegalCrossThreadCalls AndAlso Not Control.inCrossThreadSafeCall) AndAlso Me.InvokeRequired) Then
Throw New InvalidOperationException(SR.GetString("IllegalCrossThreadCall", New Object() {Me.Name}))
End If
If Not Me.IsHandleCreated Then
Me.CreateHandle()
End If
Return Me.HandleInternal
End Get
End Property
System.Windows.Forms.resources:
IllegalCrossThreadCall=Cross-thread operation not valid: Control '{0}' accessed from a thread other than the thread it was created on.
So I believe the answer would be something like this:
Whenever the handle of a control is invoked from a different thread.
(I'll give you all a vote up as all the answers are relevant towards cross-threading.)
The following code will throw an exception:
Public Class Form1
Private Sub Test(sender As Object, e As EventArgs) Handles Button1.Click
Dim t As New Thread(AddressOf Me._Proc)
t.Start()
End Sub
Private Sub _Proc(id As Integer)
Dim [error] As Exception = Nothing
Try
Dim p As IntPtr = Me.Handle
Catch ex As Exception
[error] = ex
End Try
Me.Invoke(Sub() Me._Completed([error]))
End Sub
Private Sub _Completed(ByVal [error] As Exception)
If ([error] Is Nothing) Then
MessageBox.Show("Success", Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Information)
Else
MessageBox.Show([error].Message, Me.Text, MessageBoxButtons.OK, MessageBoxIcon.Error)
Me.tbDescription.Text = [error].Message
End If
End Sub
End Class

How to pass multiple parameters in thread in VB

I'm looking to pass two or more parameters to a thread in VB 2008.
The following method (modified) works fine without parameters, and my status bar gets updated very cool-y.
But I can't seem to make it work with one, two or more parameters.
This is the pseudo code of what I'm thinking should happen when the button is pressed:
Private Sub Btn_Click()
Dim evaluator As New Thread(AddressOf Me.testthread(goodList, 1))
evaluator.Start()
Exit Sub
This is the testthread method:
Private Sub testthread(ByRef goodList As List(Of OneItem), ByVal coolvalue As Integer)
StatusProgressBar.Maximum = 100000
While (coolvalue < 100000)
coolvalue = coolvalue + 1
StatusProgressBar.Value = coolvalue
lblPercent.Text = coolvalue & "%"
Me.StatusProgressBar.Refresh()
End While
End Sub
First of all: AddressOf just gets the delegate to a function - you cannot specify anything else (i.e. capture any variables).
Now, you can start up a thread in two possible ways.
Pass an Action in the constructor and just Start() the thread.
Pass a ParameterizedThreadStart and forward one extra object argument to the method pointed to when calling .Start(parameter)
I consider the latter option an anachronism from pre-generic, pre-lambda times - which have ended at the latest with VB10.
You could use that crude method and create a list or structure which you pass to your threading code as this single object parameter, but since we now have closures, you can just create the thread on an anonymous Sub that knows all necessary variables by itself (which is work performed for you by the compiler).
Soo ...
Dim Evaluator = New Thread(Sub() Me.TestThread(goodList, 1))
It's really just that ;)
Something like this (I'm not a VB programmer)
Public Class MyParameters
public Name As String
public Number As Integer
End Class
newThread as thread = new Thread( AddressOf DoWork)
Dim parameters As New MyParameters
parameters.Name = "Arne"
newThread.Start(parameters);
public shared sub DoWork(byval data as object)
{
dim parameters = CType(data, Parameters)
}
Dim evaluator As New Thread(Sub() Me.testthread(goodList, 1))
With evaluator
.IsBackground = True ' not necessary...
.Start()
End With
Well, the straightforward method is to create an appropriate class/structure which holds all your parameter values and pass that to the thread.
Another solution in VB10 is to use the fact that lambdas create a closure, which basically means the compiler doing the above automatically for you:
Dim evaluator As New Thread(Sub()
testthread(goodList, 1)
End Sub)
In addition to what Dario stated about the Delegates you could execute a delegate with several parameters:
Predefine your delegate:
Private Delegate Sub TestThreadDelegate(ByRef goodList As List(Of String), ByVal coolvalue As Integer)
Get a handle to the delegate, create parameters in an array, DynamicInvoke on the Delegate:
Dim tester As TestThreadDelegate = AddressOf Me.testthread
Dim params(1) As Object
params(0) = New List(Of String)
params(1) = 0
tester.DynamicInvoke(params)
Just create a class or structure that has two members, one List(Of OneItem) and the other Integer and send in an instance of that class.
Edit: Sorry, missed that you had problems with one parameter as well. Just look at Thread Constructor (ParameterizedThreadStart) and that page includes a simple sample.
Pass multiple parameter for VB.NET 3.5
Public Class MyWork
Public Structure thread_Data
Dim TCPIPAddr As String
Dim TCPIPPort As Integer
End Structure
Dim STthread_Data As thread_Data
STthread_Data.TCPIPAddr = "192.168.2.2"
STthread_Data.TCPIPPort = 80
Dim multiThread As Thread = New Thread(AddressOf testthread)
multiThread.SetApartmentState(ApartmentState.MTA)
multiThread.Start(STthread_Data)
Private Function testthread(ByVal STthread_Data As thread_Data)
Dim IPaddr as string = STthread_Data.TCPIPAddr
Dim IPport as integer = STthread_Data.TCPIPPort
'Your work'
End Function
End Class
I think this will help you...
Creating Threads and Passing Data at Start Time!
Imports System.Threading
' The ThreadWithState class contains the information needed for
' a task, and the method that executes the task.
Public Class ThreadWithState
' State information used in the task.
Private boilerplate As String
Private value As Integer
' The constructor obtains the state information.
Public Sub New(text As String, number As Integer)
boilerplate = text
value = number
End Sub
' The thread procedure performs the task, such as formatting
' and printing a document.
Public Sub ThreadProc()
Console.WriteLine(boilerplate, value)
End Sub
End Class
' Entry point for the example.
'
Public Class Example
Public Shared Sub Main()
' Supply the state information required by the task.
Dim tws As New ThreadWithState( _
"This report displays the number {0}.", 42)
' Create a thread to execute the task, and then
' start the thread.
Dim t As New Thread(New ThreadStart(AddressOf tws.ThreadProc))
t.Start()
Console.WriteLine("Main thread does some work, then waits.")
t.Join()
Console.WriteLine( _
"Independent task has completed main thread ends.")
End Sub
End Class
' The example displays the following output:
' Main thread does some work, then waits.
' This report displays the number 42.
' Independent task has completed; main thread ends.
With VB 14, you can do the following with Tuples:
Shared Sub _runner(data as (goodList As List(Of OneItem), coolvalue As Integer))
Console.WriteLine($"goodList: {data.goodList}")
Console.WriteLine($"coolvalue: {data.coolvalue}")
' do stuff...
End Sub
Dim thr As New Thread(AddressOf _runner)
thr.Start((myGoodList, cval))

how to know when a work in a thread is complete?

I need to create multiple threads when a button is clicked and i've done that with this:
Dim myThread As New Threading.Thread(AddressOf getFile)
myThread.IsBackground = True
myThread.Start()
but i need to update a picture box with the downloaded file, buy if i set an event in the function getFile and raise it to notify that the files was downloaded and then update the picturebox.
Use an AsyncResult, and either check it periodically for completion, or provide a delegate to be called when the thread has completed its work.
A complete example in VB can be found here.
You need to make use of MethodInvoker deligate.
Public Sub GetFile()
If Me.InvokeRequired Then
Me.Invoke(New MethodInvoker(GetFile))
End If
End Sub
Now you can handle any event in your specified class.
You can achive that using the Asyncallback, ...
Dim sinctotal As New Del_sinc(AddressOf sincronizar)
Dim ar As IAsyncResult = sinctotal.BeginInvoke(_funcion, type, New AsyncCallback(AddressOf SincEnd), cookieobj)
The cookieobj is this
Class Cookie
Public id As String
Public AsyncDelegate As [Delegate]
Sub New(ByVal id As String, ByVal asyncDelegate As [Delegate])
Me.id = id
Me.AsyncDelegate = asyncDelegate
End Sub
End Class
When the delegate finish it will call the funcion Sincend (in this example), then you could use a event to update your picture.
Hope this helps!