How can I do the C# equivalent of await await in Visual Basic - vb.net

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).

Related

VB.Net CancellationToken does not cancel the Task

I do SAP Gui Scripting in VB.Net with .Net Framework 4.8. At some point the SAP will be unresponsive due to circumstances out of my control, and the called function will completely block further execution forever. In this case i want to safely exit the code.
In order to overcome this obstacle where the SAP function call completely blocks execution my approach is to create a New System.Threading.Task, execute the blocking function in it and after a given timeout cancel the operation. I'd also like to be informed if the element had been found.
For this reason i created the following code mostly by reading docs.mycrosoft.com
Dim propablyBlockingFn = Function ()
Dim found = False
While not found
'code that interacts with sap and will either set found to true or completly block execution
End While
Return found
End Function
Dim timeout = 5000 'ms
Dim t As Task(Of Boolean) = Task.Run(fn)
Dim cts As New CancellationTokenSource()
Dim token As CancellationToken = cts.Token
Dim taskRef = t.Wait(timeout, token)
If Not taskRef Then
cts.Cancel()
End If
Dim exists = t.Result 'it will stuck here
t.Dispose()
However, at the point where i try to read the Result of the function, the code wont execute any further and the cancel call does not have any effect.
Does anyone have an idea?
I've found a last resort solution after reading the following answer. Keep in mind that sideeffects may happen. But they would be even worse if in this case i won't abort the Thread.
Dim taskThread As Thread = Nothing
Dim propablyBlockingFn = Function ()
taskThread = Thread.CurrentThread 'Get the Thread the Task is running in
Dim found = False
While not found
'code that interacts with sap and will either set found to true or completly block execution
End While
Return found
End Function
Dim timeout = 5000 'ms
Dim t As Task(Of Boolean) = Task.Run(fn)
Dim taskRef = t.Wait(timeout)
If Not taskRef Then
taskThread.Abort() 'Abort the Thread
End If
Dim exists = t.Result

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.

Need a way to execute a powershell script asynchronosly

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.

vb.net Dispatcher.Thread doesn't always match up on loop

I'm running the below function and it's a bit hit and miss, sometimes it loops back through and runs Call ShowProgressDialog(Nothing, Nothing) other times it just does the If Me.Dispatcher.Thread Is System.Threading.Thread.CurrentThread Then and jumps straight to the Else...
Private Function CloseDownTheApplication() As Task
'-----------------------------------------------------------------------------------------------------------
' Test whether the thread that owns the control of interest is the current thread...
' If it's not, you invoke a delegate referring to the same method via the control of interest.
'-----------------------------------------------------------------------------------------------------------
If Me.Dispatcher.Thread Is System.Threading.Thread.CurrentThread Then
Call ShowProgressDialog(Nothing, Nothing)
Else
Me.Dispatcher.Invoke(New Action(AddressOf CloseDownTheApplication))
End If
End Function
Some additional info - It's initialised from a Background Worker which is initiated from worker.RunWorkerAsync() which works absolutely fine, but it is the reason I need to cross over the threads to run my code.
The ShowProgressDialog runs this code block if it matters:
Private Async Sub ShowProgressDialog(sender As Object, e As RoutedEventArgs)
Dim controller = Await Me.ShowProgressAsync("Please wait...", "We are baking some cupcakes!")
Await Task.Delay(5000)
controller.SetCancelable(False)
Dim i As Double = 0.0
While i < 6.0
Dim val As Double = (i / 100.0) * 20.0
controller.SetProgress(val)
controller.SetMessage("Baking cupcake: " & i & "...")
If controller.IsCanceled Then
Exit While
End If
'canceled progressdialog auto closes.
i += 1.0
Await Task.Delay(2000)
End While
Await controller.CloseAsync()
Me.Close()
End Sub
I've googled it to death and all I can find really is a different way to check using Dispatcher.CheckAccess but this makes no difference. Also I'm on VS Express For Windows Desktop and using VB.Net.

ContinueWith after PostAsJsonAsyc

I've got a vs2010, 4.0 vb.net, WinForms app calling AttemptLogin on a form load event.
I want to avoid blocking the form load if possible and I was hoping the tasks and continuation stuff in 4.0 would be the right way to go as I could run the main task on the default scheduler and the continuation on fromcurrentsynchronisationcontext but although I've got the OnAttemptLogin working I can't get my OnAttemptLoginCompleted function in the continuation to be called.
I think it's because the OnAttemptLogin returns a "RunToCompletion" task so the continuation never gets called. But I don't know how to deal with that, I've tried numerous things but I've now confused myself so much I'm pretty much mashing keys. Can anyone offer any advice? Am I simply doing it wrong or have I got the wrong idea all together?
Here's what I have so far, the OnAttemptLogin works as I would expect it to, but it then never calls the LongRunning tasks continuation.
Please note: I can't use await as I'm in vs2010 .net4.0 so I'm stuck with ContinueWith.
Public Sub AttemptLogin(OnAttemptLoginCompleted As Action(Of Task(Of HttpResponseMessage)))
Try
Dim LongRunningTask As Task(Of HttpResponseMessage) = Task.Factory.StartNew(Function()
Return OnAttemptLogin()
End Function, TaskScheduler.Default)
Dim UITask As Task(Of HttpResponseMessage) = LongRunningTask.ContinueWith(Sub(t)
OnAttemptLoginCompleted(t)
End Sub, TaskScheduler.FromCurrentSynchronizationContext)
LongRunningTask.Wait()
Catch ex As AggregateException
' nom nom nom
' do something useful
End Try
End Sub
Private Function OnAttemptLogin() As Task(Of HttpResponseMessage)
Dim aClient = New HttpClient()
Using (aClient)
' CREATE REQUEST
aClient.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
aClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(String.Format("{0}:{1}", CallingDTO.Email, CallingDTO.Password))))
UserQueryDTO.UserName = UserDTO.Email
UserQueryDTO.Password = UserDTO.Password
Dim url As String = DnnRequest.GetUrl(Credentials.HttpAlias, cstModuleAssembly, "User", "CanLogin", False)
' POST REQUEST
Dim p As Task(Of HttpResponseMessage) = aClient.PostAsJsonAsync(url, UserQueryDTO).ContinueWith(Function(x)
' READ RESPONSE
Dim r = x.Result.Content.ReadAsAsync(Of HttpResponseMessage)()
r.Wait()
Return r.Result
End Function)
Try
p.Wait()
Catch ex As Exception
End Try
Return p
End Using
End Function
The problem here is ... convoluted. The main issue you have here, the reason why UITask won't run, is because LongRunningTask is not of type Task(Of HttpResponseMessage). It is actually a Task(Of Task(Of HttpResponseMessage)). OnAttempLogin() returns a Task(of H...), but the task that you start in form load is a Task that will return that Task, hence, Task(Of Task(Of ...)). So there's an exception in that line, hence the UITask line never runs. So the problem with the code is that there's too many Task things all over the place.
The other problem is that you aren't really doing anything asynchronously (except that part that never ran) since you are Wait()-ing for all the tasks. So you need to get rid of most of your waits to actually achieve that. Getting rid of the waits means you need to handle exceptions with a continuation.
Some minor points:
You don't really need the scheduler stuff, either.
UITask is simply a Task, not a Task(Of ...) since it doesn't return anything.
I'm continuing from UITask to handle exceptions so that it also catches UITask's exceptions. If I continued from LongRunningTask, I would miss those exceptions.
Below is an example of what I think the new code will look like. There may be a few syntax issues since I'm missing a few things to get this to compile:
Public Sub AttemptLogin(OnAttemptLoginCompleted As Action(Of Task(Of HttpResponseMessage)))
Dim LongRunningTask As Task(Of HttpResponseMessage) = OnAttemptLogin()
Dim UITask As Task = LongRunningTask.ContinueWith(AddressOf OnAttemptLoginCompleted)
uiTask.ContinueWith(Sub(t)
Dim ex As AggregateException = t.Exception
'nom nom nom
'all your exceptions will end up here.
End Sub, TaskContinuationOptions.OnlyOnFaulted)
End Sub
Private Function OnAttemptLogin() As Task(Of HttpResponseMessage)
Dim aClient = New HttpClient()
Using (aClient)
' CREATE REQUEST
aClient.DefaultRequestHeaders.Accept.Add(New MediaTypeWithQualityHeaderValue("application/json"))
aClient.DefaultRequestHeaders.Add("Authorization", "Basic " + Convert.ToBase64String(System.Text.ASCIIEncoding.ASCII.GetBytes(String.Format("{0}:{1}", CallingDTO.Email, CallingDTO.Password))))
UserQueryDTO.UserName = UserDTO.Email
UserQueryDTO.Password = UserDTO.Password
Dim url As String = DnnRequest.GetUrl(Credentials.HttpAlias, cstModuleAssembly, "User", "CanLogin", False)
' POST REQUEST
Dim p As Task(Of HttpResponseMessage) = aClient.PostAsJsonAsync(url, UserQueryDTO).ContinueWith(Function(x)
' READ RESPONSE
Dim r = x.Result.Content.ReadAsAsync(Of HttpResponseMessage)()
r.Wait()
Return r.Result
End Function)
Try
p.Wait()
Catch ex As Exception
End Try
Return p
End Using
End Function
my solution was to delete everything and give up, i will use something else, anything else, pff at this point ill lock the ui and not care, three days on this rubbish is crazy.
Marking jtseng's reply as correct even though it didnt work as hes the only reply and deserves something for taking the time to try and help.