vb.net Async I just don't get it - vb.net

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.

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

Secondary thread causes "Application has stopped working" crashes even when invoking

I have an application which has a form with a DataGridView bound to a BindingSource, which is bound to a DataTable:
bsList.DataSource = dsData
bsList.DataMember = "List"
dgvList.DataSource = bsList
The underlying data which populates dsData.Tables("List") can change whilst the user is working so to combat this I have a background thread which routinely checks the database for changes and updates dsData.Tables("List"). It also changes the colour of any row where another user is currently working.
However, users report that when this background updating functionality is enabled the application routinely CTDs with no application error message. I have been unable to reproduce this and my attempt to log the crashes via writing to a log file in Private Sub MyApplication_UnhandledException(sender As Object, e As UnhandledExceptionEventArgs) Handles Me.UnhandledException hasn't worked as the log file is never written to, suggesting this event is never triggered.
The thread is instantiated like this:
LiveUpdating = New Thread(AddressOf UpdateUserLocation) With {.IsBackground = True}
LiveUpdating.Start()
This is the UpdateUserLocation sub:
Public Sub UpdateUserLocation()
Do While My.Settings.customBackgroundUpdating = True And formLoaded = True
UserLocations.Clear()
dtUsers = CLS_USERS.GetUsersSequence(winUser)
dtProgress = DAC.GetProgress()
For Each CandRow As DataRow In dsHHData.Tables("List").Rows
Dim CandReadDate As Date
Dim CandRowNextRead As String = DBNull.Value.ToString
If Not (CandRow("NEXT READ").ToString = DBNull.Value.ToString) Then
If Date.TryParse(CandRow("NEXT READ").ToString, CandReadDate) Then
CandRowNextRead = CandReadDate.ToString("dd/MM/yyyy")
End If
End If
Dim CandRowSending As String = TryCast(CandRow("SENDING"), String)
Dim CandRowNotes As String = TryCast(CandRow("NOTES"), String)
For Each NewRow As DataRow In dtUsers.Rows
If CandRow("SQ").ToString = NewRow("SQ").ToString Then
UserLocations.Add(NewRow("SQ").ToString)
End If
Next
For Each ProgressRow As DataRow In dtProgress.Rows
If CandRow("SQ").ToString = ProgressRow("SQ").ToString Then
Dim NextReadDate As Date
Dim ProgressRowNextRead As String = DBNull.Value.ToString
If Not (ProgressRow("NEXT READ").ToString = DBNull.Value.ToString) Then
If Date.TryParse(ProgressRow("NEXT READ").ToString, NextReadDate) Then
ProgressRowNextRead = NextReadDate.ToString("dd/MM/yyyy")
End If
End If
Dim ProgressRowSending As String = TryCast(ProgressRow("SENDING"), String)
Dim ProgressRowNotes As String = TryCast(ProgressRow("NOTES"), String)
If CandRow("SQ").ToString = ProgressRow("SQ").ToString Then
If CandRowSending <> ProgressRowSending Then
BeginInvoke(New UpdateDataTableDelegate(AddressOf UpdateDataTableSending), CandRow, ProgressRowSending)
End If
If CandRowNextRead <> ProgressRowNextRead Then
BeginInvoke(New UpdateDataTableDelegate(AddressOf UpdateDataTableNextRead), CandRow, ProgressRowNextRead)
End If
If CandRowNotes <> ProgressRowNotes Then
BeginInvoke(New UpdateDataTableDelegate(AddressOf UpdateDataTableNotes), CandRow, ProgressRowNotes)
End If
End If
End If
Next
Next
dgv.BeginInvoke(
New MethodInvoker(
Sub()
For Each dgv_row As DataGridViewRow In dgv.Rows
If UserLocations.Contains(dgv_row.Cells("SQ").Value.ToString) Then
dgv.DefaultCellStyle.BackColor = My.Settings.customRowHighlight
Else
dgv.DefaultCellStyle.BackColor = Nothing
End If
Next
End Sub))
Thread.Sleep(My.Settings.customRefreshRate * 1000)
Loop
End Sub
The subs that do the DataTable update are like this:
Private Delegate Sub UpdateDataTableDelegate(ByVal CandRow As DataRow, ByVal ProgressRow As String)
Private Sub UpdateDataTableSending(ByVal CandRow As DataRow, ByVal ProgressRowSending As String)
CandRow("SENDING") = ProgressRowSending
End Sub
I know this is not the best way to handle a multi-user environment but the nature of this work requires that all people can access and see the same data. I could force them to refresh regularly but that seems very intrusive.
The crashes only occur when this thread is running and the crashes are regular (and not instant) but I cannot seem to reproduce them and the application is very stable otherwise.
There must be some cross-threading issue but I can't work how when all of the updates to the DataTable or DataGridView are done via a BeginInvoke on the main UI thread.
EDIT: I've just realised that even though I am doing the queries and most of the heavy lifting in the background thread, the updates are stilled called on the main UI thread which would lock the thread. This would be particularly noticeable if there were a lot of updates... Because each one is called individually.
If the UI lock up was long enough, and the user was clicking on stuff, would this cause Windows to treat the application as unresponsive and crash it? If so, is there a better way I could handle these updates?
Any help with resolving this would be enormously appreciated.

Adding nodes to treeview with Begin Invoke / Invoke

I've been working through my first project and have had a great deal a valuable help from the guys on SO but now I'm stuck again.
The below sub is used to add TreeNodes to a TreeView, excluding certain filetypes/names, upon addition of new data:
Sub DirSearch(ByVal strDir As String, ByVal strPattern As String, ByVal tvParent As TreeNodeCollection)
Dim f As String
Dim e As String
Dim tvNode As TreeNode
Dim ext() As String = strPattern.Split("|"c)
Try
For Each d In Directory.GetDirectories(strDir)
If (UCase(IO.Path.GetFileName(d)) <> "BACKUP") And (UCase(IO.Path.GetFileName(d)) <> "BARS") Then
tvNode = tvParent.Add(IO.Path.GetFileName(d))
For Each e In ext
For Each f In Directory.GetFiles(d, e)
If (UCase(IO.Path.GetFileName(f)) <> "DATA.XLS") And (UCase(IO.Path.GetFileName(f)) <> "SPIRIT.XLSX") Then
tvNode.Nodes.Add(IO.Path.GetFileName(f))
End If
Next
Next
DirSearch(d, strPattern, tvNode.Nodes)
End If
Next
Catch ex As Exception
MsgBox(ex.Message)
End Try
End Sub
I'm now getting an error:
Action being performed on this control is being called from the wrong thread. Marshal to the correct thread using Control.Invoke or Control.BeginInvoke to perform this action.
On the following line:
tvNode = tvParent.Add(IO.Path.GetFileName(d))
Obviously, I understand its to do with 'threading' and the use of BeginInvoke / Invoke but even after reading the MSDN documentation on the error, I have no idea where to start.
This error only occurs, if I add a file to the initial directory (which is also the subject of a File System Watcher to monitor new additions).
Would someone be so kind as to give me an explanation in layman's terms so I may be able to understand.
This code is being run on a background thread where it's illegal to modify UI elements. The Invoke / BeginInvoke methods are ways to schedule a piece of code to run on UI thread where elements can be modified. For example you could change your code to the following
Dim action As Action = Sub() tvNode.Nodes.Add(IO.Path.GetFileName(f))
tvNode.TreeView.Invoke(action)
This code will take the delegate instance named action and run it on the UI thread where edits to tvNode are allowed
Fixing the earlier Add call is a bit trickier because there is no Control instance on which we can call BeginInvoke. The signature of the method will need to be updated to take a Dim control as Control as a parameter. You can pass in the TreeView for that parameter if you like. Once that is present the first Add can be changed as such
Dim outerAction As Action = Sub() tvNode = tvParent.Add(IO.Path.GetFileName(d))
control.Invoke(outerAction)

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.