form.Invoke(delegate) Cannot access a disposed object. .net - vb.net

Enviroment: vb.net visual studio 10 - win forms
The Error:
{"Cannot access a disposed object. Object name:'frmInfo'."}
StackTrace:
at System.Windows.Forms.Control.MarshaledInvoke(Control caller, Delegate method, Object[] args, Boolean synchronous)
at System.Windows.Forms.Control.Invoke(Delegate method, Object[] args)
at HorizonRealTime.UI.frmInfo.UpdateGUI(cHorizonColoredString strdata) in R:\csavb2010dlls\Test\TestSoloutions\StoreAutoStuff\HorizonRealTimeVB10\CommanderRealTime.UI\frmInfo.vb:line 93
at HorizonRealTime.UI.frmInfo.DoWork() in R:\csavb2010dlls\Test\TestSoloutions\StoreAutoStuff\HorizonRealTimeVB10\CommanderRealTime.UI\frmInfo.vb:line 75
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.runTryCode(Object userData)
at System.Runtime.CompilerServices.RuntimeHelpers.ExecuteCodeWithGuaranteedCleanup(TryCode code, CleanupCode backoutCode, Object userData)
at System.Threading.ExecutionContext.RunInternal(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
Simple Explanation:
I have 1 main form with many child mdi forms.
One form(frminfo) manages all the status/error messages that server processes generate.
These messages get placed on a
Imports System.Collections.Concurrent
Public oStatusMsgs As BlockingCollection(Of String) = New BlockingCollection(Of String) 'thread safe collection
The problem occurs very rarely(usually just on application startup) when it enters the if me.invokerequired { me.Invoke(delegate)} I get a object disposed error when it tries to invoke itself.
even in debug the object is not flagged as disposed after the error occurs.
if I wrap the me.invoke in a try/catch... it procedes to process the following messages just fine.
Output in textbox ends up looking like this:
---Refreshing Data---
#Items Data Refreshed#
#Deps Data Refreshed#
Real-Time Info - Error Displaying GUI Info - #Units Data Refreshed# - Cannot access a disposed object. Object name: 'frmInfo'.
#Retail Data Refreshed#
#Tax Data Refreshed#
---Exiting Data Refresh---
Whats funny is that this textbox is on frminfo that im copying the text out of.
Whats even stranger is. It is actually processing messages prior to getting this error. So the disposed error exception seems to be a false reading, and something else is actually occurring.
On frminfo is a low-priority thread that loops infinitely basically like this
Private sub DoStuff() 'this is kicked off from the frminfo.onload event as a new thread that is referenced by frminfo
dim strdata as string = nothing
do while active
If Me.frmparent.oStatusMsgs.Count > 0 Then
Me.frmparent.oStatusMsgs.TryTake(strdata, 300)
If Not strdata Is Nothing Then
Call me.UpdateGUI(strdata)
End If
strdata = Nothing
End If
thread.sleep(50)
loop
End sub
Private Sub UpdateGUI(ByVal strdata As string)
If Me.InvokeRequired Then
Try
Me.Invoke(New UpdateGUIDelegate(AddressOf UpdateGUI), New Object() {strdata})
Catch ex As Exception
Me.frmparent.oErrorMsgs.Add(Me.Text & " - Error Displaying GUI Info - " & strdata & " - " & ex.Message)
End Try
Else
'this is where it would append the strdata to some kind of textbox or other form level control
end if
end sub
On the main UI thread I do create addition references to this mdi child form.
The first is a collection I add every mdi child form to. I let the GC handle this entirely other then the add.(I do this to ensure my forms are not disposed of)
There is also a collection of forms that I use to sort the mdi childs. The
in some cases i do like
dim otmpinfofrm as form = frmcollection(i)
frmcollection(i) = nothing 'where this is where frminfo currently resides in the collection
frmcollection(i) = frmcollection(i+1)
I loop through the collection and at the end i put back in the reference to
frmcollection(lastindex) = otmpinfofrm
I dont think this sorting could impact the thread calling me.invoke to update a local control. Since Im just sorting references?
Any help would be appreciated regarding better use of the me.invoke to avoid this error from happening at all.

Related

Threadding: Update UI without blocking the thread?

I have written a server program that does a lot of jobs in threads simultaneously.
In those threads, I have to update a ListView with status information, but as it is right now using invoke, the thread waits for the UI to finish updating the ListView.
Any good advice to how I can send the status to the ListView and continue the thread while ListView finish updating?
Here's my code...
Public Delegate Sub InfoDelegate(status As String)
Public Sub Info(status As String)
If Me.InvokeRequired Then
Dim d As New InfoDelegate(AddressOf Info)
Me.Invoke(d, status)
Else
Dim item As New ListViewItem With {
.Text = status}
With lv
.BeginUpdate()
.Items.Insert(0, item)
If .Items.Count > 500 Then
For i As Integer = Me.lv.Items.Count - 1 To 500 Step -1
Me.lv.Items.RemoveAt(i)
Next
End If
.EndUpdate()
End With
End If
End Sub
You can call Control.BeginInvoke() to invoke the method asynchronously. However that call needs to be followed by a EndInvoke() call, or else you will get memory and/or thread leaks.
In the .NET Framework versions 4.0 and up you can utilize lambda expressions to pass the IAsyncResult returned from the BeginInvoke call to the lambda expression itself. Thus, you can call EndInvoke without having it block since by the time that it is called the asynchronous operation is already finished.
Here's an example:
Dim iar As IAsyncResult = _
Me.BeginInvoke(Sub()
Info("Status here") 'Calling your Info() method.
Me.EndInvoke(iar)
End Sub)

VB.NET--NullReferenceException on secondary form load

Background
I have a Winform application (VB.NET framework 4.0). Within the application are two forms: frmHome (startup form) and frmdePDF (secondary form with an Adobe PDF Reader COM control). frmHome grabs a PDF pathway from DFS, checks if the pathway actually exists, and if so, loads the file into my PDF control and shows frmdePDF. Now, this works 100% of the time on my local machine. However, the initial load on any other machine throws a NullReferenceException. Other events (e.g. my datagridview CellContentClick) that call the SAME code works fine on all machines. I understand (I hope correctly) that the issue is initializing that frmdePDF the first time.
Code
The code that loads the datagridview (dgDE) and pulls the PDF pathway:
Private Sub btnDELegalLOMCState_Click(sender As Object, e As EventArgs) Handles btnDELegalLOMCState.Click
loadStatus = "State"
Dim deState As String = Nothing
''Validate and set state
If Me.cbDEState.SelectedIndex = 0 Then
MessageBox.Show("Please select a state.")
Exit Sub
Else
deState = Me.cbDEState.Text
End If
''Populate dgDE/set data bindings
Try
Me.cbDERegion.ComboBox.SelectedIndex = -1
Me.dgDE.DataSource = Nothing
Me.SpSelectLOMAbyStateTableAdapter.Fill(Me.DevGISDataSet.spSelectLOMAbyState, 4, deState)
Me.dgDE.DataSource = Me.SpSelectLOMAbyStateBindingSource
Me.bnDE.BindingSource = Me.SpSelectLOMAbyStateBindingSource
Me.txtDEIssueDte.DataBindings.Clear()
Me.txtDECaseNum.DataBindings.Clear()
Me.txtDECommNum.DataBindings.Clear()
Me.txtDEIssueDte.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.SpSelectLOMAbyStateBindingSource, "IssueDte", True))
Me.txtDECaseNum.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.SpSelectLOMAbyStateBindingSource, "CaseNum", True))
Me.txtDECommNum.DataBindings.Add(New System.Windows.Forms.Binding("Text", Me.SpSelectLOMAbyStateBindingSource, "CommNum", True))
Catch ex As Exception
MessageBox.Show("Error loading LOMAs: " & ex.Message.ToString)
End Try
''Load PDF
If Me.dgDE.RowCount <> 0 Then
Dim i As Integer = Me.dgDE.CurrentRow.Index
Dim pathdePDF As String = Me.dgDE.Rows.Item(i).Cells(5).Value.ToString
LoaddePDF(pathdePDF)
End If
End Sub
The Public Sub that contains code to load the file and show frmdePDF:
Public Sub LoaddePDT(ByVal pathdePDF As String)
If System.IO.File.Exists(pathdePDF) Then
frmdePDF.pdfLOMC.LoadFile(pathdePDF)
frmdePDF.Show()
Else
MessageBox.Show("Image not available. Please check FEMA and CAMSIS.")
frmdePDF.Hide()
End If
End Sub
Other things I have tried
Creating a new instance of the form each time:
Public Sub LoadPDF_test(ByVal pathdePDF As String)
Dim openForms = Application.OpenForms.OfType(Of frmdePDF)()
While openForms.Any
openForms.First.Close()
End While
If System.IO.File.Exists(pathdePDF) Then
Dim newfrmdePDF As New frmdePDF
newfrmdePDF.pdfLOMC.LoadFile(pathdePDF)
newfrmdePDF.Show()
Else
MessageBox.Show("Image not available. Please check FEMA and CAMSIS.")
End If
End Sub
This code works on my machine 100%. It behaves the same way as the first load code on any other machine (throws the NullReferenceException).
Exception Text
See the end of this message for details on invoking
just-in-time (JIT) debugging instead of this dialog box.
************** Exception Text **************
System.NullReferenceException: Object reference not set to an instance of an object.
at SaveLOMC.frmHome.LoadPDF_test(String testPath) in C:\Data\Alycia\Development\LOMC\Projects\TFS_test\SaveLOMC\SaveLOMC\frmHome.vb:l ine 46
at SaveLOMC.frmHome.btnDENewLOMCState_Click(Object sender, EventArgs e) in C:\Data\Alycia\Development\LOMC\Projects\TFS_test\SaveLOMC\SaveLOMC\frmHome.vb:l ine 440
at System.Windows.Forms.ToolStripItem.RaiseEvent(Object key, EventArgs e)
at System.Windows.Forms.ToolStripMenuItem.OnClick(EventArgs e)
at System.Windows.Forms.ToolStripItem.HandleClick(EventArgs e)
at System.Windows.Forms.ToolStripItem.HandleMouseUp(MouseEventArgs e)
at System.Windows.Forms.ToolStripItem.FireEventInteractive(EventArgs e, ToolStripItemEventType met)
at System.Windows.Forms.ToolStripItem.FireEvent(EventArgs e, ToolStripItemEventType met)
at System.Windows.Forms.ToolStrip.OnMouseUp(MouseEventArgs mea)
at System.Windows.Forms.ToolStripDropDown.OnMouseUp(MouseEventArgs mea)
at System.Windows.Forms.Control.WmMouseUp(Message& m, MouseButtons button, Int32 clicks)
at System.Windows.Forms.Control.WndProc(Message& m)
at System.Windows.Forms.ScrollableControl.WndProc(Message& m)
at System.Windows.Forms.ToolStrip.WndProc(Message& m)
at System.Windows.Forms.ToolStripDropDown.WndProc(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.OnMessage(Message& m)
at System.Windows.Forms.Control.ControlNativeWindow.WndProc(Message& m)
at System.Windows.Forms.NativeWindow.Callback(IntPtr hWnd, Int32 msg, IntPtr wparam, IntPtr lparam)
I have tried stepping through the debugger on my local machine. But again, I can't recreate the error on it so the objects all have assigned values.
I know this question has been asked endlessly. I have read through all of these (plus MORE!):
What is a NullReferenceException, and how do I fix it?
"Object reference not set to an instance of an object?"
Object reference not set to an instance of an object. vb.net looking up a string
visual basic: object reference not set to an instance of an object
Visual Basic - Object reference not set to an instance of an object
VB.Net How to set an object reference to an instance of an object?
object reference not set to an instance of an object. vb.net for some but not all code
Object reference not set to an instance of an object.
specific error on :"Object reference not set to an instance of an object."
Object reference not set to an instance of an object
"Object reference not set to an instance of an object" message
object reference not set to an instance of an object error showing
Additional information: my local machine is 64 bit, the other machines are 32 bit. The associated .dll files for the Adobe component are 32 bit. My Target CPU in the Application Properties is x86.
Any advice is appreciated, I wouldn't ask this broad question if I hadn't exhausted my options.
Solution
There was nothing wrong with the form load event--I was trying to manually set the location/bounds of the form on load. Set the bounds to a non-existent screen (my computer has three screens--everyone else has 2). Learning experience?

Handling an exception thrown from inside a dll

I'm working on a project for school in which a dll is loaded.
The dll that is loaded is a bridge between my program and the Twincat System Manager which forms the bridge between the computer and a PLC via the local network.
I need to read Variables trough this whole chain from the plc to my program.
This is the way I do this:
Public Function adsReadReal(ByVal Variabelenaam As String) As Single
Dim ds = New TwinCAT.Ads.AdsStream(4 * 8) ' new data stream
Dim br = New System.IO.BinaryReader(ds) 'new binary
Dim hVar = New Integer
Try
ConState(1)
tcclient = New TcAdsClient
ConState(2)
tcclient.Connect(Form1.amsAdress, 801) 'connects the tcclient to the PLC
hVar = tcclient.CreateVariableHandle(Variabelenaam) 'creats a handle for the variable
tcclient.Read(hVar, ds) 'read it
ConState(5)
Return br.ReadSingle() 'convert it from binary to readable for vb
Catch ex As Exception
ConState(0)
PrintEx(ex) 'print out the exception
finally
tcclient.Dispose() 'make the object stop being used to prevent a lingering connection
End Try
Return False
End Function
Now the program loads a dll called TwinCAT.ADS.dll at the start of the connection module. If the Twincat system manager is running the program ends normally, but when it is not it crashes and gives me this error:
System.DllNotFoundException was unhandled
Message="Kan DLL tcadsdll.dll niet laden: Kan opgegeven module niet vinden. (Uitzondering van HRESULT: 0x8007007E)"
Source="TwinCAT.Ads"
TypeName=""
StackTrace:
bij TwinCAT.Ads.Internal.TcAdsDllWrapper.TcAdsDll.AdsAmsUnRegisterRouterNotification()
bij TwinCAT.Ads.Internal.TcAdsDllWrapper.AmsUnRegisterRouterNotification(Boolean
throwAdsException)
bij TwinCAT.Ads.Internal.TcLocalSystem.Dispose(Boolean disposing)
bij TwinCAT.Ads.Internal.TcLocalSystem.Finalize()
which is roughly translated to:
Cannot load DLL tcadsdll.dll: Cannot find given module. (Exception at
HRESULT: 0x8007007E)
This is not a dll that I have imported, so it must be from the TwinCAT.ADS.dll
How can I prevent the program from throwing this error at me and instead close the program peacefully? I have tried to catch all the exceptions of every dll related operation possible.
Also the source is on Bitbucket. I will make it public on request.
Some links on the official but quite unhandy Beckhoff site:
http://infosys.beckhoff.com/espanol.php?content=../content/1034/tcquickstart/html/tcquickstart_samplevisualbasicnet.htm&id=10449
Edit:
Apparently using tcclient.dispose() causes the error since the finnaly statement was use instead of just after the try block
Edit: This currently catches the exception but it does not handle it.
Dim currentDomain As AppDomain = AppDomain.CurrentDomain
AddHandler currentDomain.UnhandledException, AddressOf MyHandler
Dim tick As Byte = 0
Sub MyHandler(sender As Object, args As UnhandledExceptionEventArgs)
Dim ex As Exception = DirectCast(args.ExceptionObject, Exception)
MsgBox("exception tick" & Str(tick))
tick = tick + 1
PrintEx(ex)
End Sub
Edit:
The exception isn't caught properly because in vs2008 a couple of errors occurs but the tick appears after I press F5 (continue)
When the program is run directly, I only see 1 Tick. Then windows gives an error.
Did you try an unhandled exception handler?
Dim currentDomain As AppDomain = AppDomain.CurrentDomain
AddHandler currentDomain.UnhandledException, AddressOf MyHandler
Sub MyHandler(sender As Object, args As UnhandledExceptionEventArgs)
Dim e As Exception = DirectCast(args.ExceptionObject, Exception)
Console.WriteLine("MyHandler caught : " + e.Message)
End Sub

Why am I getting a "Cross-thread operation not valid exception" in the callback method when using AsyncCallback with BeginInvoke?

I have a windows form that gets data from a scale via the serial port. Since I need the operator to be able to cancel the process I do the get data process on a second thread.
The data collection process gets multiple readings from the scale one at a time. The form has a label that needs to be updated with information specific to each reading.
I call the method to get the data from the scale with this code.
Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)
The delegate for the readALine method is defined in the UI code.
Delegate Function readALine(ByVal toSend As String) As String
The GetDataCallback method:
Private Sub GetDataCallback(ByVal ia As IAsyncResult)
.
.
.
lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
lblInstructions.Refresh()
.
.
.
End Sub
I get the exception on the "lblInstructions.Text =" statement.
I thought the GetDataCallback method was part of the UI thread so don't understand why I'm getting the exception. I know this could probably be rewritten using a BackgroundWorker and it's appropriate events but for now would like to understand why this isn't working as expected.
The application was written originally in VS2003 and just recently upgraded to VS2008.
Any insight will be appreciated.
Thanks,
Dave
The problem is a confusion over BeginInvoke. Calling Control.BeginInvoke marshals a delegate call to the UI thread. Calling BeginInvoke on a delegate causes it to be executed on a thread pool thread - and any callback you provide is executed on the same (thread pool) thread. The latter is what you're doing, which is why GetDataCallback is being executed on a thread pool thread, not the UI thread..
So, within GetDataCallback you need to call Control.Invoke or Control.BeginInvoke to marshal back to the UI thread.
One point to note: I very rarely use Control.InvokeRequired these days - it's simpler to unconditionally call Invoke/BeginInvoke; the performance difference isn't usually enough to make up for the benefit in readability, in my experience.
user-interface controls can only be updated by the thread that created them
try
yourForm.BeginInvoke
instead; that should marshall the call to the correct thread
In .NET 1.1 it was possible to perform these cross-thread operations, even though they weren't safe.
In .NET 2.0 the exception you mention is thrown, when you try to perform cross-thread operations such as this, which means that you are performing the UI operations on a non-UI thread, even though you thought you weren't.
Try using the Me.InvokeRequired method to determine whether you are currently on the UI thread. E.g. you could define a method to perform the necessary tasks:
Protected Sub PerformUIOperations()
If Me.InvokeRequired Then
'We are currently on a non-UI thread. Invoke this method on the UI thread:
Me.Invoke(New MethodInvoker(AddressOf Me.PerformUIOperations))
Return
End If
'Perform UI operations when we know it is safe:
lblInstructions.Text = "blabla"
End Sub
The PerformUIOperations method can then be called from any non-UI thread, since it takes care of performing the tasks on the correct thread itself.
Of course, if you need to pass parameters to the PerformUIOperations method (such as information regarding the ongoing operation) you'll have to define a delegate to match the PerformUIOperations method signature and use this instead of the MethodInvoker.
Jon,
I have the following in another section of the code:
Delegate Sub setValueCallback(ByVal row As Integer, ByVal value As Decimal)
Public Sub setValue(ByVal row As Integer, ByVal value As Decimal)
If Me.Controls.Item(_tbIndex(row - 1)).InvokeRequired Then
Dim d As New setValueCallback(AddressOf setValue)
Me.Invoke(d, New Object() {row, value})
Else
Dim tb As TextBox = Me.Controls.Item(_tbIndex(row - 1))
tb.Text = value
_dt.Rows(tb.Tag).Item(1) = value
End If
End Sub
How would this be rewritten to not use .InvokeRequired?
Dave, maybe this is the solution you are looking for:
Dim ad As New readALine(AddressOf thisScale.readALine)
Dim ac As New AsyncCallback(AddressOf Me.GetDataCallback)
Dim iar As IAsyncResult = ad.BeginInvoke("", ac, ad)
Delegate Function readALine(ByVal toSend As String) As String
Private Sub GetDataCallback(ByVal ia As IAsyncResult)
If lblInstructions.InvokeRequired Then
lblInstructions.Invoke(New AsyncCallback(AddressOf GetDataCallback), New Object() {ia})
Else
.
.
lblInstructions.Text = _thisMeasure.dt.Rows(_currRow - 1).Item("cnt") & ":"
lblInstructions.Refresh()
.
.
End If
End Sub

IndexOutOfRangeException in VB.NET

Picked up a legacy VB.NET project originally written for version 1.1 of the .NET framework. I'm running Vista with .NET 3.5. I have cleared out all the original error and the project will build; it just wont run.
As far as I can tell, it is trying to run 'LoginForm' but putting breakpoints in doesn't work because the error is thrown before the breakpoints are reached, regardless of where in the file they are placed.
Really can't work out what to do! Any help appreciated.
StackTrace:
System.IndexOutOfRangeException was unhandled
Message="Index was outside the bounds of the array."
Source="FirstLine"
StackTrace:
at FirstLine.LoginForm.main()
at System.AppDomain._nExecuteAssembly(Assembly assembly, String[] args)
at System.AppDomain.ExecuteAssembly(String assemblyFile, Evidence assemblySecurity, String[] args)
at Microsoft.VisualStudio.HostingProcess.HostProc.RunUsersAssembly()
at System.Threading.ThreadHelper.ThreadStart_Context(Object state)
at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state)
at System.Threading.ThreadHelper.ThreadStart()
InnerException:
EDIT: Terribly sorry, didn't appreciate the code would be of much use because the issue is more that I can't get to it. However, here's the main function:
Shared Sub main()
Dim p As Process() = Process.GetProcessesByName("FirstLine")
If p.Length = 1 Then
'START COPYMINDER
'Dim expirydate As Date = CDate("01/01/1970")
'Dim expiry As Integer
'Try
' GetCopyMinderExpiryDate(expiry)
' If Not expiry = 0 And Not expiry = 1 Then
' expirydate = expirydate.AddSeconds(expiry)
' Dim diff As Integer = DateDiff(DateInterval.Day, Date.Now, expirydate)
' If diff >= 0 And diff 0 Then
' DisplayError((ret_code))
' End
'End If
'Dim did As String
'GetCopyMinderDeveloperID(did)
'If did "IT" Then
' MessageBox.Show("Invalid Developer ID " & did & ". Firstline will now shutdown", "Firstline", MessageBoxButtons.OK, MessageBoxIcon.Error)
' End
'End If
'END COPYMINDER
Dim lf As New LoginForm
If LoginSettings.setting("loginShowErrorOnLine") = "TRUE" Then
lf.ShowDialog()
Else
Try
lf.ShowDialog()
Catch ex As Exception
MsgBox(ex.Message)
Config.UnlockByUser(Config.currentUser.username)
Config.currentUser.UserLoggedOff()
End Try
End If
Else
Dim prc As Process = p(0)
SwitchToThisWindow(prc.MainWindowHandle, True)
End If
End Sub
Thanks for your responses so far. It's encouraging to see such a helpful community!
Dim prc As Process = p(0) is your problem as it is in the else statements where the the array length can be anything but 1 (0 for instance).
when the length is 0 it will give you the IndexOutOfRange when you try to access the first element.
Try turning on debug build; this will give you the possibility to add breakpoints and will also give you some row numbers at the stack trace.
Your problem is that you are not handling the case where p.Length = 0. This occurs if there is no process with the name "FirstLine".
Have you also renamed your process / application ?