TPL null reference visual basic task - vb.net

I'm developing some control software for a robot in Visual Basic and I cannot for the life of me figure out why the execution of a procedure, which works flawlessly outside of the Task framework, does not work when I try to run it concurrently to the rest of the code. I'm trying to use the TPL to let a certain procedure run in the background while allowing the user to use the GUI in parallel (currently, the program freezes during execution of this procedure, and isn't active again until it's over, and this can take hours). The problem here that I couldn't solve by looking at other questions is, my NullReferenceException happens inside the Task area, but if I don't try to run it concurrently to the rest of the software, the exception never, ever happens. This is the code I'm using:
Dim taskSPME = Task(Of Boolean).Factory.StartNew(Function()
Return MainForm.startSPME((SPMEpanel.SPMECoordinates), SPMEpanel.WaitingTimes, SPMEpanel.WellVolumes, SPMEpanel.StirPosition, SPMEpanel.StirringSpeed, SPMEpanel.StirringRadius, SPMEpanel.HoverZ, SPMEpanel.GridScaling, SPMEpanel.IncrementWithin)
End Function)
Try
taskSPME.Wait()
MsgBox("Task return value: " & taskSPME.Result.ToString)
Catch ex As AggregateException
For Each fault In ex.InnerExceptions
If TypeOf (fault) Is InvalidOperationException Then
MsgBox("Invalid Operation Exception")
ElseIf TypeOf (fault) Is NullReferenceException Then
MsgBox("Null Reference Exception. " & fault.Message.ToString)
ElseIf TypeOf (fault) Is IO.FileNotFoundException Then
MsgBox("File not found Exception")
End If
Next
End Try
startSPME(), located in another GUI class called MainForm, is a function that runs fine and returns a boolean value if it's not called from any sort of parallel/concurrent framework. I've tried using Parallel.Invoke() and even the old ThreadPool stuff, and they all just throw the same exception, which happens to be the NullReferenceException where the fault message says "Object reference not set to an instance of an object". I tried performing trivial tasks inside the StartNew() argument, like a MsgBox for example, and those run fine. The imports are
Imports System.Threading
Imports System.Threading.Tasks
Imports System.Threading.Thread
Can someone tell me what I'm forgetting here? Are there conditions for the task called by the StartNew() constructor to perform? Like, can it not be located in another class? Originally, I didn't even want to call the Wait() method on my task, since I want the program to go back to the GUI while the robot performs its experiments, but since nothing happens to the robot unless the startSPME() function is outside of that parallel task code, I have no idea what's going wrong here.

Related

Can RubberDuck test if a program ends?

I'm developing tests with RubberDuck, and would like to test MsgBox outputs from a program. The catch is that the program ends right after outputting the MsgBox - there's literally an "End" Statement.
When running a RubberDuck test and using Fakes.MsgBox.Returns, there's an inconclusive yellow result with message "Unexpected COM exception while running tests"
I've tried placing an "Assert.Fail" at the end of the test; however, it seems like the program ending throws things off.
Is it possible for a test in RubberDuck to detect if the program ends?
tldr; No
Rubberduck unit tests are executed in the context of the VBA runtime - that is, the VBA unit test code is being run from inside the host application. Testing results are reported back to Rubberduck via its API. If you look at the VBA code generated when you insert a test module, it gives a basic idea of the architecture of how the tests are run. Take for example this unit test from our integration test suite:
'HERE BE DRAGONS. Save your work in ALL open windows.
'#TestModule
'#Folder("Tests")
Private Assert As New Rubberduck.AssertClass
Private Fakes As New Rubberduck.FakesProvider
'#TestMethod
Public Sub InputBoxFakeWorks()
On Error GoTo TestFail
Dim userInput As String
With Fakes.InputBox
.Returns vbNullString, 1
.ReturnsWhen "Prompt", "Second", "User entry 2", 2
userInput = InputBox("First")
Assert.IsTrue userInput = vbNullString
userInput = InputBox("Second")
Assert.IsTrue userInput = "User entry 2"
End With
TestExit:
Exit Sub
TestFail:
Assert.Fail "Test raised an error: #" & Err.Number & " - " & Err.Description
End Sub
Broken down:
This creates a managed class that "listens" for Asserts in the code being tested and evaluates the condition for passing or failing the test.
Private Assert As New Rubberduck.AssertClass
The FakesProvider is a utility object for setting the hooks in the VB runtime to "ignore" or "spoof" calls from inside the VB runtime to, say, the InputBox function.
Since the Fakes object is declared As New, the With block instantiates a FakesProvider for the test. The InputBox method of Fakes This sets a hook on the rtcInputBox function in vbe7.dll which redirects all traffic from VBA to that function to the Rubberduck implementation. This is now counting calls, tracking parameters passed, providing return values, etc.
With Fakes.InputBox
The Returns and ReturnsWhen calls are using the VBA held COM object to communicate the test setup of the faked calls to InputBox. In this example, it configures the InputBox object to return a vbNullString for call one, and "User entry 2" when passed a Prompt parameter of "Second" for call number two.
.Returns vbNullString, 1
.ReturnsWhen "Prompt", "Second", "User entry 2", 2
This is where the AssertClass comes in. When you run unit tests from the Rubberduck UI, it determines the COM interface for the user's code. Then, it calls invokes the test method via that interface. Rubberduck then uses the AssertClass to test runtime conditions. The IsTrue method takes a Boolean as a parameter (with an optional output message). So on the line of code below, VB evaluates the expression userInput = vbNullString and passes the result as the parameter to IsTrue. The Rubberduck IsTrue implementation then sets the state of the unit test based on whether or not the parameter passed from VBA meets the condition of the AssertClass method called.
Assert.IsTrue userInput = vbNullString
What this means in relation to your question:
Note that in the breakdown of how the code above executes, everything is executing in the VBA environment. Rubberduck is providing VBA a "window" to report the results back via the AssertClass object, and simply (for some values of "simply") providing hook service through the FakesProvider object. VBA "owns" both of those objects - they are just provided through Rubberduck's COM provider.
When you use the End statement in VBA, it forcibly terminates execution at that point. The Rubberduck COM objects are no longer actively referenced by the client (your test procedure), and it's undefined as to whether or not that decrements the reference count on the COM object. It's like yanking the plug from the wall. The only thing that Rubberduck can determine at this point is that the COM client has disconnected. In your case that manifests as a COM exception caught inside Rubberduck. Since Rubberduck has no way of knowing why the object it is providing has lost communication, it reports the result of the test as "Inconclusive" - it did not run to completion.
That said, the solution is to refactor your code to not use End. Ever. Quoting the documentation linked above End...
Terminates execution immediately. Never required by itself but may be placed anywhere in a procedure to end code execution, close files opened with the Open statement, and to clear variables.
This is nowhere near graceful, and if you have references to other COM objects (other than Rubberduck) there is no guarantee that they will be terminated reliably.
Full disclosure, I contribute to the Rubberduck project and authored some of the code described above. If you want to get a better understanding of how unit testing functions (and can read c#), the implementations of the COM providers can be found at this link.

How to recover from loss of connection to a (locally hosted) DCOM object?

(I'm not sure if this is would be better suited for code review)
I'm using a (closed source) library that maintains a DCOM connection internally, this DCOM connection goes to a service running on the local machine. This service is stopped and started quite frequently by the user (not through our application).
Unfortunately it looks like DCOM related exceptions bubble up to our application.
There doesn't seem to be any way given in the library to determine if the connection to the service is still valid, right now the only way to know we've lost connection to the service to try to do something and have it throw an exception (the type of which varies significantly).
My best attempt to try to make this a little more manageable is to separate out exceptions that are usually caused by DCOM from exceptions that are caused by configuration problems....
Here's a sample of what the code ended up looking like:
Private Sub Initialize()
Dim ParametersToDComObject As Integer
Try
Dim HRESULT = ClassThatUsesDCOMInstance.InitDCOMObject(ParametersToDComObject)
Console.WriteLine("Connected to service through DCOM.")
Catch Ex As Exception
If (IsLikelyCausedByConnectionLoss(Ex)) Then
'in this case, throw to make sure that we don't try to continue assuming that the connection went through
'I'm not sure what type of exception would be best to throw here,
'this would occur if the service is not running; the calling function
'would notify the user that there's a problem
Throw New InvalidOperationException(Ex.Message, Ex)
Else
Throw
End If
End Try
End Sub
Private Sub ReadDataFromClass(EntryName As String)
Try
Dim HRESULT As Integer = ClassThatUsesDCOMInstance.ReadEntry(EntryName)
Catch Ex As Exception
If (IsLikelyCausedByConnectionLoss(Ex)) Then
'If the service is stopped, from the application's perspective every method in
'this class suddenly starts failing, and it won't start working again until a new instance
'of the class that uses DCOM is made.
'
'So the sub below attempts to make sure that our application knows that the methods in this class
'won't work anymore... (I usually set ClassThatUsesDCOMInstance to Nothing after cleaning up)
'
'Sometimes I just have to "reconnect", but the tricky thing is, the only recovery option may be to
'(re)start the service that's providing the DCOM object; it seems best to notify the
'user in this case since in most cases the user knowlingly stopped the service, it's
'normal to stop the service to configure it
CleanupAfterConnectionLoss()
Throw New InvalidOperationException(Ex.Message, Ex)
End If
If (TypeOf (Ex) Is TheLibraryDefinedThisException) Then
'let's say that the library throws this if we try to read an entry that doesn't exist (e.g., if the configuration has changed)
'we can't fix the problem automatically, but we can notify the user that something has changed external to this program, so they can
'either fix the service or edit their configuration for our application
Console.WriteLine("Entry " & EntryName & " could not be read " & Ex.Message & " check that the entry is defined on the service")
Else
Throw
End If
End Try
End Sub
Private Function IsLikelyCausedByConnectionLoss(ThrownException As Exception) As Boolean
'I'm thinking it's best to put this in a separate function because development has gone like this:
'
' 1. the provided documentation does not give any information about what methods will throw what kinds of exceptions
' All functions return **HRESULTs**, I assumed that there weren't going to be /any/ exceptions
'
' It turns out the HRESULTs cover certain errors and exceptions cover certain errors, with some overlap...
'
' 2. I determined experimentally during testing that it throws a <Library Defined Exception> if I give it an invalid entry name, okay, I can work with that
'
' 3. I determined experimentally that if I stop the service while we're connected, all functions throw a COMException, not convenient but ok.
'
' 4. Weeks later, I figure out that it also occasionally throws InvalidComObjectException if the service is stopped under the same situation
'
' 5. Weeks later, I find out that it occasionally throws ApplicationExceptions under the same situation
'
' 6. Weeks later, I find out that certain functions will additionally throw an InvalidCastException
'
' note that there's no change in input to the library to cause these different exceptions, and the only cause
' is the loss of connection to the DCOM server
'
' in the last code setup I had to manually update every try/catch block that had anything to do with this library every time
' I found something new...
If (TypeOf (ThrownException) Is InvalidCastException) Then
'I really don't like catching this, but I get this if the COM object fails to bind to the interface,
'which occasionally happens if the service isn't there or isn't set up right
Return True
ElseIf (TypeOf (ThrownException) Is ApplicationException) Then
'I really don't like catching this either, same reason as above
Return True
'I don't believe this is an exhaustive list of every exception that can be thrown due to loss of DCOM connection,
'please let me know if you know of more exceptions that can be thrown...
ElseIf (TypeOf (ThrownException) Is Runtime.InteropServices.COMException) Then
Return True
ElseIf (TypeOf (ThrownException) Is Runtime.InteropServices.InvalidComObjectException) Then
Return True
Else
Return False
End If
End Function
I feel like this is a poor solution for the problem, though. It seems better than just swallowing all exceptions but not by much.
Is there any exception that encompasses all possible DCOM errors?
Alternatively, is there some other way to detect that a connection to a service has been lost?
EDIT:
Is this situation typical for DCOM in .NET?

How to make SendKeys act Synchronously in IBM Host Access Library

I use the IBM Host Access Class Library for COM Automation as a way to communicate with an IBM AS400 (aka iSeries, IBM i, green screen, 5250) through a terminal emulator. I notice that when you issue a "SendKeys" instruction, control returns to your application before the IBM emulator finishes with the command. This can lead to timing problems because you might then send another "SendKeys" instruction before the system is ready to accept it.
For example:
Imports AutPSTypeLibrary
Imports AutConnListTypeLibrary
Imports AutSessTypeLibrary
Sub Example
Dim connections As New AutConnList
connections.Refresh()
If connections.Count < 1 Then Throw New InvalidOperationException("No AS400 screen can currently be found.")
Dim connection As IAutConnInfo = DirectCast(connections(1), IAutConnInfo)
_Session = New AutSess2
_Session.SetConnectionByHandle(connection.Handle)
Dim _Presentation As AutPS = DirectCast(_Session.autECLPS, AutPS)
_Presentation.SendKeys("PM70[enter]", 22, 8)
_Presentation.SendKeys("ND71221AD[enter]", 22, 20)
End Sub
would work correctly when stepping through code in a debugger, but would fail when running normally because the second instruction was sent too soon.
One way to work with this is to put a timer or loop after each command to slow the calling program down. I consider this less than ideal because the length of time is not always predictable, you will often be waiting longer than necessary to accommodate an occasional hiccup. This slows down the run time of the entire process.
Another way to work around this is to wait until there is a testable condition on the screen as a result of your sent command. This will work sometimes, but some commands do not cause a screen change to test and if you are looking to abstract your command calling into a class or subroutine, you would have to pass in what screen condition to be watching for.
What I would like to find is one of the "Wait" methods that will work in the general case. Options like the autECLScreenDesc class seem like they have to be tailored to very specific conditions.
The autECLPS (aka AutPS) class has a number of Wait methods (Wait, WaitForCursor, WaitWhileCursor, WaitForString, WaitWhileString, WaitForStringInRect, WaitWhileStringInRect, WaitForAttrib, WaitWhileAttrib, WaitForScreen, WaitWhileScreen) but they also seem to be waiting for specific conditions and do not work for the general case. The general case it important to me because I am actually trying to write a general purpose field update subroutine that can be called from many places inside and outside of my .dll.
This example is written in VB.NET, but I would expect the same behavior from C#, C++, VB6, Java; really anything that uses IBM's Personal Communications for Windows, Version 6.0
Host Access Class Library.
The "Operator Information Area" class seems to provide a solution for this problem.
My general case seems to be working correctly with this implementation:
Friend Sub PutTextWithEnter(ByVal field As FieldDefinition, ByVal value As String)
If IsNothing(field) Then Throw New ArgumentNullException("field")
If IsNothing(value) Then Throw New ArgumentNullException("value")
_Presentation.SendKeys(Mid(value.Trim, 1, field.Length).PadRight(field.Length) & "[enter]", field.Row, field.Column)
WaitForEmulator(_Session.Handle)
End Sub
Private Sub WaitForEmulator(ByVal EmulatorHandle As Integer)
Dim Oia As New AutOIATypeLibrary.AutOIA
Oia.SetConnectionByHandle(EmulatorHandle)
Oia.WaitForInputReady()
Oia.WaitForAppAvailable()
End Sub
I give thanks to a user named "khieyzer" on this message board for pointing our this clean and general-purpose solution.
Edit:
After a few weeks debugging and working through timing and resource release issues, this method now reads like:
Private Sub WaitForEmulator(ByRef NeededReset As Boolean)
Dim Oia As New AutOIA
Oia.SetConnectionByHandle(_Presentation.Handle)
Dim inhibit As InhibitReason = Oia.InputInhibited
If inhibit = InhibitReason.pcOtherInhibit Then
_Presentation.SendKeys("[reset]")
NeededReset = True
WaitForEmulator(NeededReset)
Marshal.ReleaseComObject(Oia)
Exit Sub
End If
If Not Oia.WaitForInputReady(6000) Then
If Oia.InputInhibited = InhibitReason.pcOtherInhibit Then
_Presentation.SendKeys("[reset]")
NeededReset = True
WaitForEmulator(NeededReset)
Marshal.ReleaseComObject(Oia)
Exit Sub
Else
Marshal.ReleaseComObject(Oia)
Throw New InvalidOperationException("The system has stopped responding.")
End If
End If
Oia.WaitForInputReady()
Oia.WaitForAppAvailable()
Marshal.ReleaseComObject(Oia)
End Sub

thread pool error trapping

Checking on a best practice here.
I am calling a ThreadPool to run an Async process, specifically sending an SSL email with an attachment which seems to take a good long while.
This is just test code, I thought I would drop this in a Try Catch just incase something failed. But will the thread ever even come back here? I have tested this closing the web page, the browser, clicking the back button on the page. The email always makes it through.
Try
System.Threading.ThreadPool.QueueUserWorkItem(AddressOf DoAsyncWork) '
Catch ex As Exception
Throw ex
Exit Sub
End Try
I am not trying to force a failure, yet I guess, but I would like to know how best to trap if the thread fails.
Protected Sub DoAsyncWork(ByVal state As Object)
Dim oMyObject As New sendSSLemail
oMyObject.SSL(userName, , strMessageBody, emailAdd, , permFileLocation, , "CodeMsg")
End Sub
A more convenient way of doing work with the thread pool is to use Task.Factory.StartNew(Action). It returns a Task object which can be Awaited or blocked on with Wait.
Once the task completes, the Exception property of the Task can be used to determine whether an exception was thrown and unhandled by the task's subroutine. (If it's not Nothing, then the InnerException property has the real exception that was thrown.)
Dim task = Task.Factory.StartNew(AddressOf WorkFunction)
' do stuff that doesn't depend on WorkFunction having completed
task.Wait()
If task.Exception IsNot Nothing Then Throw task.Exception.InnerException
After a Throw, the sub is exited anyway (the call stack is unwound looking for a Catch), so the Exit Sub statement does nothing. Wrapping a QueueUserWorkItem call in a try block also does nothing, because any exception would occur on the other thread. You would only get exceptions thrown immediately by QueueUserWorkItem, which off the top of my head only includes complaints about the delegate being Nothing.
Asynchronous tasks can also return values. For more information on that, see the TaskFactory methods that return a Func(Of Task).

Load, Use and the Unload an Assembly at runtime

I am working on a project where I have to load assemblies (lets call them tasks) at runtime, run the task and then be able to kill off the whole assembly so that the dll can be replaced without interrupting the main application.
There are many of these tasks running within the main application, some run sequentially and some run in parallel. Occasionally one or to of these tasks need to be updated and then re-added to the queue. Currently we are stopping the entire application and interrupting the other tasks at whatever stage they are at, which is not ideal.
I have figured out that I will have to load each assembly into a separate AppDomain, but loading the assemblies in those domains is proving difficult, especially when I need to actually run the tasks and receive events from them. I have been looking into this problem for a couple of days and have still not managed to get a working proof-of-concept.
I have an Interface for the tasks which includes a 'run' and 'kill' method (subs) and a 'taskstep', 'complete' and 'killed' event. 'taskstep' returns an object to be cached later, 'complete' fires when the whole task is done and 'killed' fires when it is ready to be unloaded. There should also be a timeout on the whole process of two hours and a timeout of 2 minutes on the killed event in case it gets stuck, at which point I would like to be able to unload it, forcing any threads to terminate (which is what 'kill' should do anyway). Each assembly may contain several tasks, all of which should loadable be unloadable.
I have no problems loading these tasks as 'plugins' but am lost when trying to both use them and unload them. If I have to create some elaborate wrapper then so be it but is what I need even possible?
I have tried inheriting from MarshalByRefObject but I do not even know the assembly fullname unless I load it first, which then locks the file. I have tried loading from a byte array of the assembly. This means that the file is not locked but a copy of it remains in the current appdomain. This will become problematic over the following months / years!
For Each key As String In assemblies.Keys
Dim ad As AppDomain = AppDomainHelper.BuildChildAppDomain(AppDomain.CurrentDomain, key)
AddHandler ad.AssemblyResolve, AddressOf AssemblyResolve
_l.Add(ad)
For Each value As String In assemblies(key)
Dim item As IScanner = CType(ad.CreateInstanceAndUnwrap(key, value), IScanner)
ListBox1.Items.Add(item)
Next
Next
Private Function AssemblyResolve(sender As Object, args As ResolveEventArgs) As Assembly
Return GetType(IScanner).Assembly
End Function
Consider using Managed Extensibility Framework.
It's part of .Net 4.0, and you can read a short overview here.
Loading and unloading assemblies can be a very bad idea - things doesn't work as your might expect (ever try to catch an exception thrown in another AppDomain? BAM you can't!)
It appears that exception is thrown from the new (secondary, if you will) AppDomain. Which means that the ResolveEventArgs have to be serialize to cross appdomain boundary. You should handle the event from within the secondary appdomain so you dont have to cross that boundary.
so... create a class that handles the resolutions.. something like...
Public Class AssemblyResolver
Inherits MarshalByRefObject
Public Property SearchPath As String = String.Empty
Public Function ResolveAssembly(sender as Object, e As ResolveEventArgs) As Assembly
Dim tPath As String = Path.Combine(SearchPath, e.Name & ".dll")
If File.Exists(tPath) Then
Return Assembly.LoadFrom(tPath)
End If
Return Nothing
End Function
End Class
Now use it from your plugin loader...
Dim tPluginDomain As AppDomain = AppDomainHelper.BuildChildAppDomain(AppDomain.CurrentDomain, key)
Dim tResolver As AssemblyResolver = tPluginDomain.CreateInstanceAndUnwrap(GetType(AssemblyResolver).Assembly.FullName, GetType(AssemblyResolver).FullName)
AddHandler tPluginDomain.AssemblyResolve, AddressOf tResolver.ResolveAssembly
That should get you pointed down the right road.
All code was off the top of my head with reference to MSDN. DOnt know if it will compile.. expect to typo/debug it.
EDIT
Whoops.. forgot to tag the resolver into the plugin domain. Fixed.