Need a way to execute a powershell script asynchronosly - vb.net

On a Windows 7 PC, I'm building a Windows Forms application with
Visual Studio 2015 Enterprise
VB.net
.Net Framework 4.5.2
PowerShell 5.0
The application involves multithreading because it has an embedded PowerShell script that takes a fairly long time to run. Embedded here means that the fillListOfStringVarAsync() function (shown below) builds a string variable with the PowerShell script text. A second thread runs fillListOfStringVarAsync() and the main thread places the returned data from this function on a main form control. I first used the BackgroundWorker approach, with the DoWork handler, etc. This works perfectly. Then I read about the newer async / await approach; I rebuilt everything using async / await and I hit a wall. This sample
Imports System.Management.Automation
Public Class demo
Private Async Sub form_Load(sender As Object, e As EventArgs) Handles MyBase.Load
' Some statements
Dim listOfStringVar As List(Of String) = Await fillListOfStringVarAsync()
' Some statements that use the
'
' listOfStringVar
'
' value
End Sub
Private Async Function fillListOfStringVarAsync() As Task(Of List(Of String))
Dim scriptText As String = "#PowerShell 5.0 script, known to work"
Dim stringListVar As New List(Of String)
Dim psInstance As PowerShell = PowerShell.Create().AddScript(scriptText)
Dim async As IAsyncResult = psInstance.BeginInvoke() 'await won't work here
For Each obj As PSObject In psInstance.EndInvoke(async) 'await won't work here
stringListVar.Add(obj.ToString)
Next
Return stringListVar
End Function
End Class
shows the basic engineering of the rebuild, with async / await. The problem: inside the fillListOfStringVarAsync() function, the psInstance object - which has the PowerShell script - needs an await expression, but I don't know the correct syntax for this. Visual Studio green-underlines the function name fillListOfStringVarAsync() with a warning that
This async method lacks 'Await' operators and so will run asynchronously etc. etc. etc.
The app compiles, but without the await operator in fillListOfStringVarAsync(), the main form disappears until the secondary thread completes and returns its data. This page and this page, both from Microsoft, brought me the closest. I looked all over StackOverflow but no luck.

You can use
await Task.Run(() => YourMethod());
I'm not sure that will help though. At least in web apps, awaiting will still stop your form from showing up until the async method has completed.

Related

How can I do the C# equivalent of await await in Visual Basic

I have a VB function which starts a program as a process and waits for it to complete. It passes a return code of zero if okay or 8 if not. The problem is that it blocks the WPF UI thread and can lead to crashes of Not Enough Quota because the thread has been blocked for too long.
So I’m trying to make it run async so the UI thread isn’t blocked. Unfortunately I have many lines of VB code but all the examples on various web sites these days are C# which I don’t program in and I have far too much code to try and learn C# to convert it all.
I’ve tried Await Task.Run which doesn’t accept parameters. I temporarily removed the Pgm parameter and hardcoded the program name and it will then compile and work. I realise I could use global variables instead but that doesn’t seem good practice.
TaskFactory seems to allow parameters but when I await on StartNew control returns immediately because StartNew creates an outer task and an inner task and the Await only waits for the initial outer task. A C# solution I’ve found suggests using Await Await Task but I can’t seem to convert this to a syntax that VB will accept.
Any help would be appreciated on how I can Await for Startit to complete. I'm using .Net 6 and VS 2022 under Windows 10.
Please excuse any formatting errors. This is my first day on Stack Overflow
The code
Class MainWindow
Private Sub Button_Click(sender As Object, e As RoutedEventArgs)
Call StartitAsync()
MsgBox("Returned from StartitAsync")
End Sub
Private Async Function StartitAsync() As Task(Of Integer)
Dim Startup As Func(Of String, Integer) = AddressOf Startit
Dim tf As New TaskFactory
Dim Rc As Integer = Await tf.StartNew(Startup, "notepad.exe")
MsgBox("Returned from await of Startit, RC is " & Rc)
Return Rc
End Function
Private Function Startit(Pgm As String) As Integer
Dim RC As Integer
Dim Startinfo As New ProcessStartInfo
MsgBox("Pgm is " & Pgm)
Startinfo.WindowStyle = ProcessWindowStyle.Maximized ' Display in a maximised window
Startinfo.FileName = Pgm
Startinfo.Arguments = ""
Using PgmProcess As Process = Process.Start(startInfo:=Startinfo) ' Start the program
PgmProcess.WaitForExit() ' Wait until it ends
If PgmProcess.HasExited = True Then ' If the process has exited
RC = PgmProcess.ExitCode ' Save the exit code
Else
RC = 8
End If
End Using
Return RC
End Function
You should use Task.Run instead of Task.Factory.StartNew (or (new TaskFactory()).StartNew). This is true for C# as well as VB.NET.
My VB is extremely rusty, but I believe this is what you're looking for:
Private Async Function StartitAsync() As Task(Of Integer)
Dim Startup = Function() Startit("notepad.exe")
Dim Rc As Integer = Await Task.Run(Startup)
MsgBox("Returned from await of Startit, RC is " & Rc)
Return Rc
End Function
This uses lambda expressions, which are very useful when using APIs like Task.Run.
Side note: You shouldn't call MsgBox from Startit. Since Task.Run executes Startit on the thread pool, it shouldn't access any UI elements (or do things like show message boxes).

vb.net Async I just don't get it

Public Function PiesTableTest(compairFile As String, version1 As String, Optional silent As Boolean = False) As Boolean
Dim dpgs As New frmDetailProgress
Dim retturn As Boolean
PiesThreadedTableTest(compairFile, version1, silent, dpgs)
End Function
Async Function PiesThreadedTableTest(compairFile As String, version1 As String, silent As Boolean, dpgs As frmDetailProgress) As Task(Of Boolean)
Dim ctl() As xmlControlAry
Dim xmlDoc As XElement
Dim xmlNodes As IEnumerable(Of XElement)
Dim notfound(0) As String
version = version1
nodeErrors = False
If Not silent Then
dpgs.lblTital.Text = "Pies Configuration Check"
dpgs.add("Pies Version = " & version)
dpgs.add("Loading Config Data....")
dpgs.Show()
End If
' load configuration data
GetPiesControl(ctl, version)
' load test xml file
xmlDoc = XElement.Load(compairFile)
xmlNodes = xmlDoc.Elements()
For Each ele As XElement In xmlNodes
NodeDrill("", ele, ctl, dpgs, notfound, silent)
Next
If nodeErrors And Not silent Then
dpgs.add("Testing done with Errors!!!", "R")
Else
dpgs.add("Testing Done NO ERRORS!", "G")
End If
Application.DoEvents()
If silent Then
dpgs.Dispose()
End If
'PiesThreadedTableTest = Not nodeErrors
If nodeErrors Then
Return False
Else
Return True
End If
End Function
I am trying to understand multi threading. frmDetailProgress is a "please wait " kind of form. and i have a animated gif on it. Plus it has a check box to close automatically after completion. Well the form is frozen till the process is done. I am trying to get the piesthreadedtabletest to run in another thread. I have read allot on this but i just don't understand the concept. I don't understand the await function enough to make this work. i get that await is designed to stop processing until something happens. But i want that form freed up to work. I get an error saying that the function will run synchronously unless i have an await - Why?
I got it working. It was a lack of understanding and i probably still need to learn more. I hope this will help someone in the future.
i created a class to call functions in the other class running in the second thread.
imports system.threading
public sub callThreadedProcedure()
dim tp as system.threading.thread ' this will be for the object running in the other thread
dim objectToRun as myclass ' this is the object you want to run in the thread
'this gets the object and puts it into the new thread
tp = new thread(sub() objectToRun.FunctionToRun(<put your parameters here if any>))
' start execution of the object in a new thread.
tp.start()
' that will get it to run in a separate thread. It works, there might be a better way
' and might not work in all situations, but for now it fixed my problem.
end sub
if you are trying to run functions in the original thread you need to pass a
reference to that object to the one in the second thread. you must then use invoke to run a function or sub from the second thread.
Invoke(sub() obj.function(<parameters>))
thanks Idle_mind invoked worked like it should.
I appreciate all that helped me along.

A few simple questions about functions that have no references being greyed out in visual studio 2019?

Not everything though
Public Shared Async Function getMarketDetailFromAllExchangesAsync() As Task
Dim taskList = New List(Of Task)
Dim starttime = jsonHelper.currentTimeStamp
LogEvents("Start Getting Market Detail of All")
For Each account In uniqueAccounts().Values
Dim newtask = account.getMarketInfoAsync().ContinueWith(Sub() account.LogFinishTask(MethodBase.GetCurrentMethod().Name, starttime))
taskList.Add(newtask)
'newtask.ContinueWith(Sub() LogEvents(account.ToString))
Next
Await Task.WhenAll(taskList.ToArray)
Dim b = 1
'Await getPairsPriceStepForAllAccountsAsync()
End Function
Private Shared Async Function getPairsPriceStepForAllAccountsAsync() As Task
For Each account In uniqueAccounts()
Await account.Value.getPairsPriceStepAsync()
Next
End Function
getPairsPriceStepForAllAccountsAsync is greyed out. I know it's because it has no reference and can be deleted safely. However, the function getMarketDetailFromAllExchangesAsync also have 0 references and it's black.
I wonder why?
Both can be commented out safely.
I wonder if I can easily found such functions too
It is because one is Private and one is Public. A Private method not called within the same class is guaranteed not to be called anywhere, while a Public method could be called in some other non-accessible code.

Assigning a reference type object a value on a secondary thread, and then working with this object from my primary thread

I am trying to create a variable which is of type MyReferenceTypeObject and of value null on thread one, use a delegate to make this thread equal to a new instance of MyReferenceTypeObject on thread two, and then access members of MyReferenceTypeObject back on thread one (in the delegates callback method).
My code is below:
Module Module1
Delegate Sub EditReferenceTypePropertyDelegate(ByVal referenceTypeObject As MyReferenceTypeObject, ByVal numberToChangeTo As Integer)
Sub Main()
Dim editReferenceDelegate = New EditReferenceTypePropertyDelegate(AddressOf EditReferenceTypeProperty)
Dim newReferenceTypeObject As MyReferenceTypeObject
editReferenceDelegate.BeginInvoke(newReferenceTypeObject, 2, AddressOf EditReferenceCallback, newReferenceTypeObject)
Console.ReadLine()
End Sub
Sub EditReferenceTypeProperty(ByVal referenceTypeObject As MyReferenceTypeObject, ByVal numberToChangeTo As Integer)
referenceTypeObject = New MyReferenceTypeObject()
referenceTypeObject.i = numberToChangeTo
End Sub
Sub EditReferenceCallback(ByVal e As IAsyncResult)
Dim referenceObject = DirectCast(e.AsyncState, MyReferenceTypeObject)
Console.WriteLine(referenceObject)
End Sub
End Module
Class MyReferenceTypeObject
Public Property i As Integer
End Class
However, newReferenceTypeObject comes into my callback method as null. I think I understand why, but the problem is that I need to pull some data from a database which I then need to pass into the constructor of newReferenceTypeObject, this takes a couple of seconds, and I don't want to lock up my UI while this is happening. I want to create a field of type MyReferenceTypeObject on thread one, instantiate this on thread two (after I have pulled the data of the database to pass into the constructor) and then work with members of the object back on thread one once the instantiation is complete.
Is this possible? I am using VB.Net with .Net 4.0 on Visual Studio 2012.
If you want to keep the GUI responsive during a long running action, I'd consider using the Task<> library (Comes with .NET 4.0). Here's a quick example.
Sub Main()
Dim instantiateTask = New Task(Of MyReferenceTypeObject)(Function()
' Call your database to pull the instantiation data.
Return New MyReferenceTypeObject With {.i = 2}
End Function)
instantiateTask.Start() ' Start the task -> invokes a ThreadPool.Thread to do the work.
instantiateTask.ContinueWith(Sub(x)
Console.WriteLine(x.Result.I)
End Sub, TaskScheduler.FromCurrentSynchronizationContext())
End Sub
.Wait blocks the GUI thread. However, you could use ContinueWith which is async and therefor nonblocking. Also you need to provide the TaskScheduler ( TaskScheduler.FromCurrentSynchronizationContext ) from the GUI thread to prevent cross-thread exceptions in case you want to update the UI from within the async method.

Using the Exited event in vb.net

Ok, I'm making a very basic vb.net winforms app, essentially you can drag files into it, and it then uses a batch file to process the files.
It's pretty simple and everything is going to plan so far, it accepts the right files, it uses the batch file to process them and the batch file does what it is supposed to.
The only problem is that I don't know how to hook into the Exited event that can/should be raised by the batch file process when the process completes...
I want the DOS window of the batch file to remain hidden while it is running, so I have used ProcessStartInfo to specify the batch file, then set the WindowStyle property of the process to ProcessWindowStyle.Minimised, then used System.Diagnostics.Process.Start(myBatch) to start the process at the appropriate moment.
This is fine, it works and does what I want. However, the only way to tell when a process ends is to use the Exited event. But the Exited event apparently only works with a Process not a ProcessStartInfo. I could switch to use Process instead but then I couldn't (AFAIK) run the DOS window minimised...
Is there a way around this? I've only been writing .net for a few days. This is how I'm running the batch file:
Dim myBatch As New ProcessStartInfo("C:\\batchFiles\\test.bat")
myBatch.WindowStyle = ProcessWindowStyle.Minimized
system.Diagnostics.Process.Start(myBatch)
Any ideas?
Thanks
Try creating a process object and setting the StartInfo property. You can then call WaitForExit instead of waiting for the event. EG:
using(var process = new Process
{
StartInfo =
new ProcessStartInfo("Foo.exe")
{WindowStyle = ProcessWindowStyle.Minimized}
})
{
process.Start();
process.WaitForExit();
}
Not sure of the syntax in VB but I am almost sure that what you have to do is actually use the WIN API inline with managed code, and then you can use the MainWindowHandle of the Process Object.
[DllImport("User32")]
private static extern int ShowWindow(int hwnd, int nCmdShow);
The commands it takes, I would recommend reference to the win api library for this method. But what you want to do I would think is very feasible with the interop.
Andrew
From the documentation: This event can occur only if the value of the EnableRaisingEvents property is true.
So the following should work:
Dim procStart As New ProcessStartInfo
Dim WithEvents proc As New Process
Private Sub Button21_Click(sender As System.Object, e As System.EventArgs) Handles Button21.Click
procStart.FileName = "C:\PTL\Bin\xxxxxx.exe"
proc.StartInfo = procStart
proc.EnableRaisingEvents = True
proc.Start()
End Sub
Private Sub proc_Exited(sender As Object, e As System.EventArgs) Handles proc.Exited
Debug.WriteLine("Process Ended " + proc.ExitCode.ToString + " " + DateTime.Now.ToString)
End Sub