Update a label from a task - vb.net

I'm trying to implement tasks in my program. I launch a task that will produce a log file, and after, I want to update the label to say "Log sucessfully saved".
Here is my code
Private Function Createlog(ByVal mylist As List(Of classTest))
Dim sw As New StreamWriter("log_list.log")
For index = 1 To mylist.Count - 1
sw.WriteLine(mylist(index).comments)
Next
sw.Close()
Try
Me.Invoke(UpdateLabel("Log sucessfully saved"))
Catch ex As Exception
End Try
Return 1
End Function
Private Function UpdateLabel(ByVal text As String)
Label1.Text = text
Return 1
End Function
I launch the task from the Main form in the Load() :
Dim tasktest = Task(Of Integer).Factory.StartNew(Function() Createlog(theList))
(I don't know if it is better to use the factory or declare as a task and then task.Start())
I have the error on the label update :
Cross-thread operation not valid: Control 'Label1' accessed from a thread
other than the thread it was created on.
Could you please explain why it doesn't work with the invoke method ? And do you have an alternative solution ?
Thanks for your help

First, UpdateLabel should be a Sub, not a Function. Second, this line is wrong:
Me.Invoke(UpdateLabel("Log sucessfully saved"))
Read it again. You are, in order, executing the UpdateLabel function, then passing the result of that function to Me.Invoke (if you used Sub instead of Function, the compiler should have warned you about the error).
This doesn't raise any compiler errors because a Function declared without As [Type] is defaulted to As Object, that can be cast to anything. It should be:
Me.Invoke(Sub()
UpdateLabel("Log sucessfully saved")
End Sub)
To simplify, your code can be rewritten like this:
Private Sub Createlog(ByVal mylist As List(Of classTest))
Dim sw As New StreamWriter("log_list.log")
For index = 1 To mylist.Count - 1
sw.WriteLine(mylist(index).comments)
Next
sw.Close()
Me.Invoke(Sub()
Label1.Text = "Log sucessfully saved"
End Sub)
End Sub

Related

Thrown exception stopping application despite being in a try block

I have an async function that makes a call to an API, but sometimes there is bad data and I want an exception to be thrown to stop other subsequent procedures from having to run. The Async procedure looks like this:
public async Function getInfo(url as string) as task(of string)
Dim htpRes As HttpResponseMessage = Await url.GetAsync().ConfigureAwait(False)
Dim result = htpRes.Content.ReadAsStringAsync.Result
If result = "" Then
Throw New Exception("API Failed")
Else
Return result
End If
End Function
That function is called by a procedure that looks like this:
sub hitAllAPIs(apiList As List(Of String))
For each i In apiList
Try
Dim info As String = getInfo(i)
doOtherStuffWithInfo(info)
Catch ex As Exception
logError
End Try
Next
End sub
The desired behavior is for the forloop in 'hitAllAPIs' to keep running even if an exception is thrown within 'getInfo'. Instead, what happens is that the exception gets hit and stops the code from running, whether I'm in Debug mode or Release mode. If I'm not there to babysit it and hit 'continue' then the forloop will just stop and the program won't run anymore. Once I hit 'continue', btw, the 'Catch' will work and the error will be logged.
The issue is that I need this to all happen automatically and that's not happening. I can't just eliminate the exception and check the function for a null value, since this is a very simplified version of my code and the function is actually called all over the place. I know that I can change my exception settings to simply skip over all exceptions like this, but this is happening even in Release mode to code that has been deployed. I can't imagine that my debugging exceptions should have an effect on code deployed in Release mode. In any case, I'm hoping someone can help me understand why this exception isn't being automatically handled by the try block.
Thanks!
It seems result = "" is an expected result not an exception. Using Try/Catch is rather heavy handed. Exception handling is for unexpected results. Get rid of the Throw in the Function and add and If in the For Each.
Public Async Function getInfo(url As String) As Task(Of String)
Dim htpRes As HttpResponseMessage = Await url.GetAsync().ConfigureAwait(False)
Dim result = htpRes.Content.ReadAsStringAsync.Result
Return result
End Function
Sub hitAllAPIs(apiList As List(Of String))
For Each i In apiList
Dim info As String = getInfo(i)
If info = "" Then
'Add an overload of logError that accepts a string
logError("API failed")
Else
doOtherStuffWithInfo(info)
End If
Next
End Sub

Task is running and cannot be finished

Have strange behaviour in my task which is not finishing. I use this all the time but i suppose its because sub i am passing to it is iteracting with form - changing selection and refreshing some listbox probably therefore its stack there but i am not sure. Lets see the code:
This is the sub i want to be run in task:
Public Sub UnselectExistingConnectionsItems()
Dim SentenceId, SubSubKategorieId, SubSectionId As Integer
SubSectionId = CbSubSections.SelectedValue 'combobox
If WithSubSubkategorie = SubSubKategorieEnum.Without Then
SubSubKategorieId = 0
Else
SubSubKategorieId = CbSubSubKategorie.SelectedValue 'combobox
End If
Unselect:
For i As Integer = 0 To LB_Sentences.SelectedItems.Count - 1
Dim sKey As ListBoxItem
sKey = LB_Sentences.SelectedItems(i)
SentenceId = HtmlDescription.HtmlSentence.GetSentenceIdByName(sKey.Text)
If HtmlDescription.HtmlSubSubSections_Sentences.CheckIfConnectionAlreadyExist(SentenceId, SubSectionId, SubSubKategorieId) Then
sKey.IsSelected = False
LB_Sentences.Refresh()
GoTo Unselect
End If
Next
End Sub
i put it to Task like this:
Dim pic As New FrmCircularProgress(eCircularProgressType.Line)
Dim work As Task = Task.Factory.StartNew(Sub()
'--Run lenghty task UnselectExistingConnectionsItems()
'--Close form once done (on GUI thread)
pic.Invoke(New Action(Sub() pic.StopCircular()))
pic.Invoke(New Action(Sub() pic.Close()))
End Sub)
'--Show the form
pic.ShowDialog()
Task.WaitAll(work)
and FrmCircularProgress is just form ( i use it almost everywhere where i have to user wait and its working besides this particural case):
Public Class FrmCircularProgress
Sub New(progressType As DevComponents.DotNetBar.eCircularProgressType)
InitializeComponent()
CircularProgress1.ProgressBarType = progressType
StartCircular()
End Sub
Public Sub StartCircular()
Me.CircularProgress1.IsRunning = True
End Sub
Public Sub StopCircular()
Me.CircularProgress1.IsRunning = False
End Sub
End Class
what could be wrong? is it because procedure is interacting with listbox and combobxes? If so how to fix that, i read something about invoking listbox and comboboxes but have no idea how to fix that.
EDIT:
I think besides those lines:
sKey.IsSelected = False
LB_Sentences.Refresh()
I have to make those:
LB_Sentences.Invoke(Sub() sKey.IsSelected = False
End Sub)
LB_Sentences.Invoke(Sub() LB_Sentences.Refresh()
End Sub)
because i am in diffrent thread. Somehow i dont know how to convert those lines:
SubSectionId = CbSubSections.SelectedValue
SubSubKategorieId = CbSubSubKategorie.SelectedValue
probably loop also have to be invoked. Waiting your help.
There is a rule that says "The only thread that can modify a control in a window is the thread that created the window". Any other thread trying to modify something in the window will generate a cross-thread call exception.
So in your first edit you got it right, you have to invoke the functions.
However, this doesn't fix your problem of not finishing Task.
I believe that doing sKey.IsSelected = False does not unselect anything in your ListBox, therefore causing an infinite loop... Also that Goto statement is very bad programming habits and should not be used. There is always another solution that will make your code easier to debug/maintain/read...
ListBoxItem is not a type that exists in the .Net Framework. So either you created that class either it's something else (and I don't know what...)
What you can do to solve your problem is :
Get the indices of all selected items in a list
Run through your list, and check if they should be selected :
If they should be selected, do nothing
if they shouldn't, unselect them.
Which makes your code like this (and you remove that ugly Label and Goto that you don't want in your code)...
Public Sub UnselectExistingConnectionsItems()
Dim SentenceId, SubSubKategorieId, SubSectionId As Integer
SubSectionId = CbSubSections.SelectedValue 'combobox
If WithSubSubkategorie = SubSubKategorieEnum.Without Then
SubSubKategorieId = 0
Else
SubSubKategorieId = CbSubSubKategorie.SelectedValue 'combobox
End If
'We create an array to remind our initial selection
Dim sel = New Integer(LB_Sentences.SelectedItems.Count - 1) {}
LB_Sentences.SelectedIndices.CopyTo(sel, 0)
For i = 0 To sel.Length - 1
Dim sKey As ListBoxItem
'We get our selected item
sKey = LB_Sentences(sel(i))
SentenceId = HtmlDescription.HtmlSentence.GetSentenceIdByName(sKey.Text)
If HtmlDescription.HtmlSubSubSections_Sentences.CheckIfConnectionAlreadyExist(SentenceId, SubSectionId, SubSubKategorieId) Then
'We must remove it from the selection
LB_Sentences.Invoke(Sub() LB_Sentences.SelectedItems.Remove(sKey))
End If
Next
'We do the Refresh at the end so we gain some process time...
LB_Sentences.Invoke(Sub() LB_Sentences.Refresh())
End Sub

Threading: Invoke gets stuck .NET

I have a system tray application. The tray application has an icon and a context menu with some options. There is a menu called status which contains below toolstripmenuitems:
Start
Restart
Stop
They are enabled/disabled according to some conditions.
My system tray application, has a background thread does continuously check some conditions and do some work. Its main loop is below:
Do Until gAppExit Or Me.thdExit
' Check some conditions and do some work
Loop
gAppExit is a global variable that indicates whether user has exited from application through the 'exit' toolstripmenuitem.
thdExit indicates whether the thread should exit from the loop (I explain it later).
When user want to restart the background thread, he clicks on restart toolstripmenuitem and below sequence is done (Restart -> Halt -> WaitFinish):
Public Function ReStart() As Integer
Dim result As Integer
If IsNothing(ThreadBgWorker) Then
Logger.Write("Task is not yet created!", LOGGING_CRITICAL_ERRORS_CATEGORY)
Return RESULT_ERROR
End If
result = Me.Halt()
If result <> RESULT_ERROR Then
result = Me.Start()
End If
Return result
End Function
Public Function Halt() As Integer
Dim result As Integer
If IsNothing(ThreadBgWorker) Then
Logger.Write("Task is not yet created!", LOGGING_CRITICAL_ERRORS_CATEGORY)
Return RESULT_ERROR
End If
Me.thdExit = True
result = Me.WaitFinish()
Return result
End Function
Public Function WaitFinish() As Integer
Dim result As Integer
If IsNothing(ThreadBgWorker) Then
Return RESULT_ERROR
End If
result = RESULT_ERROR
Try
'TODO:
Console.WriteLine("Wait thread to finish (before Join)...")
Me.ThreadBgWorker.Join()
'TODO:
Console.WriteLine("Thread finished... Continue restarting thread...")
Me.RetVal = True
result = Me.ThreadBgWorker.ManagedThreadId
Logger.Write(String.Format("Task ""{0}"" correctly stopped.", _
Me.ThreadBgWorker.Name))
Catch ex As Exception
Logger.Write(String.Format("Couldn't stop task ""{0}"": {1}", _
Me.ThreadBgWorker.Name, ex.Message), _
LOGGING_CRITICAL_ERRORS_CATEGORY)
End Try
Return result
End Function
Public Function Start() As Integer
Dim result As Integer
If IsNothing(ThreadBgWorker) Then
Logger.Write("Task is not yet created!", LOGGING_CRITICAL_ERRORS_CATEGORY)
Return RESULT_ERROR
End If
result = RESULT_ERROR
Me.thdExit = False
Try
If Me.ThreadBgWorker.ThreadState = Threading.ThreadState.Stopped Then
Me.Create()
End If
Me.ThreadBgWorker.Start() ' Start the new thread.
result = Me.ThreadBgWorker.ManagedThreadId
Logger.Write(String.Format("Task ""{0}"" correctly started.", _
Me.ThreadBgWorker.Name))
Catch ex As Exception
Logger.Write(String.Format("Couldn't start task ""{0}"": {1}", _
Me.ThreadBgWorker.Name, ex.Message), _
LOGGING_CRITICAL_ERRORS_CATEGORY)
End Try
Return result
End Function
Note that on Halt function, it awaits thread to finish by calling Me.ThreadBgWorker.Join() on function WaitFinish. Before calling WaitFinish function, thdExit is set to true in order to background thread can exit from main loop:
Do Until gAppExit Or Me.thdExit
' Check some conditions and do some work
Loop
ChangeStatusToStopped()
on exit loop, ChangeStatusToStopped() is called, and it is as below:
Private Delegate Sub ChangeStatusToStoppedDelegate()
Public Sub ChangeStatusToStopped()
' TODO:
Console.WriteLine("Changing status to stopped...")
System.Windows.Forms.Application.DoEvents()
If MainMenu.InvokeRequired Then
'TODO:
Console.WriteLine("Invoke required!")
MainMenu.Invoke(New ChangeStatusToStoppedDelegate(AddressOf ChangeStatusToStopped))
Else
'TODO:
Console.WriteLine("Invoke NOT required!")
Me.submnuStart.Enabled = True
Me.submnuReStart.Enabled = False
Me.submnuStop.Enabled = False
End If
' TODO:
Console.WriteLine("Status changed to stopped.")
End Sub
What it does is to enable Start toolstripmenuitem and disable restart and stop toolstripmenuitems in the UI.
The problem is:
Within ChangeStatusToStopped method, when MainMenu.InvokeRequired is true, it calls:
MainMenu.Invoke(New ChangeStatusToStoppedDelegate(AddressOf ChangeStatusToStopped))
and then it gets stuck there, that is, else body:
Me.submnuStart.Enabled = True
Me.submnuReStart.Enabled = False
Me.submnuStop.Enabled = False
is never executed. It seems like main thread is busy or some other problem in message pump.... Any ideas?
I have seen that line:
Me.ThreadBgWorker.Join()
in WaitFinish() function is reached before background thread exits main loop and despite thdExit has been set to true before doing Me.ThreadBgWorker.Join(), once join is performed, application gets stuck, background thread cannot exit main loop (seems application is busy or frozen).

Call Function when User Types Certain Keyword

Okay, so I am working on a small scripting language using a VB Console Application.
I want the user to input "say('something')" and it calls the function I made named "say", is there a way to call the function and still use the following code:
Module Module1
Sub say(sayline)
Console.WriteLine(sayline)
End Sub
Sub Main()
Dim cmd As String
Console.WriteLine(">")
Do
Console.Write("")
cmd = Console.ReadLine()
If cmd IsNot Nothing Then cmd
Loop While cmd IsNot Nothing
End Sub
End Module
No, you cannot just call a method from user's string. You need to interpret the entered data.
First, you need to split your method name and arguments so that entered "say('something')" will transform to say and something. Remember that user can enter wrong data and you need to check if this call is correct - it's all about syntactic and lexical analysis. I hope you understand how to do this because it is pretty difficult.
Then, you need to check if you have a method called say. In case of plain and simple structure, switch construction will be enough. If your have such method, then pass something argument to this method. Else, output something like "unknown method".
If you wanted to call the method say upon typing the word say(something) and display the word something, then you can just have a certain condition that if the user types the word say within the input then call say method else, do whatever you want to do under else portion. Parse the input and omit the word say from the input and display it then.
You can have your code this way for example. (I just copied your code and added some codes to meet what you wanted... in my understanding)
Module Module1
Sub say(ByVal sayline)
Console.WriteLine(sayline)
End Sub
Sub Main()
Dim cmd As String
Do
Console.Write("> ")
cmd = Console.ReadLine()
Try
If cmd IsNot Nothing And cmd.Substring(0, 3).ToUpper().Equals("SAY") Then
say(parseInput(cmd))
End If
Catch ex As Exception
Console.WriteLine("message here")
End Try
Loop While cmd IsNot Nothing
End Sub
Function parseInput(ByVal cmd As String) As String
Dim input As String = ""
For index As Integer = 3 To cmd.Length - 1
If Char.IsLetter(cmd) Then
input += cmd.Substring(index, 1)
Else
input = input
End If
Next
Return input
End Function
End Module

Shared Resource in Parallel.ForEach

How do I control access to a shared resource in a Parallel.ForEach loop? I am trying to download multiple files in parallel, and I want to capture information about downloads that fail, so that the user can re-attempt the download later. However, I am worried that if more than one download fails at the same time, the application will throw an exception because one thread will attempt to access the file while it is being written to by another.
In the code below, I would like to know how to control access to the file at RepeateRequestPath. A RequestSet is a list of strings that represent IDs of the resource I am trying to download.
Dim DownloadCnt As Integer = 0
Dim ParallelOpts As New ParallelOptions()
ParallelOpts.MaxDegreeOfParallelism = 4
Parallel.ForEach(RequestSets, ParallelOpts, Sub(RequestSet)
Try
DownloadCnt += 1
Dim XmlUrl As String = String.Format("{0}{1}{2}", "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=", String.Join(",", RequestSet), "&retmode=xml&rettype=abstract")
DownloadFile(XmlUrl, String.Format("{0}\TempXML{1}.xml", XMLCacheDir, DownloadCnt))
Catch ex As WebException
Using Response As WebResponse = ex.Response
Dim statCode As Integer = CInt(DirectCast(Response, HttpWebResponse).StatusCode)
MessageBox.Show(String.Format("Failed to retrieve XML due to HTTP error {0}. Please hit the 'Retrieve XML' button to re-run retrieval after the current set is complete.", statCode))
If Not File.Exists(RepeatRequestPath) Then
File.WriteAllLines(RepeatRequestPath, RequestSet)
Else
File.AppendAllLines(RepeatRequestPath, RequestSet)
End If
End Using
End Try
End Sub)
The usual way to protect a shared resource in VB.NET is to use SyncLock.
So, you would create a lock object before the Parallel.ForEach() loop:
Dim lock = New Object
and then you would use that inside the loop:
SyncLock lock
File.AppendAllLines(RepeatRequestPath, RequestSet)
End SyncLock
Also note that you can use AppendAllLines() even if the file doesn't exist yet, so you don't have to check for that.
You need to use a semaphore to control access to a shared resource. You want only one thread to access the error file at one time, so initialize the semaphore to only allow 1 thread in. Calling _pool.WaitOne should seize the semaphore, and then release it once it finishes creating/writing to the file.
Private Shared _pool As Semaphore
_pool = = New Semaphore(0, 1)
Dim DownloadCnt As Integer = 0
Dim ParallelOpts As New ParallelOptions()
ParallelOpts.MaxDegreeOfParallelism = 4
Parallel.ForEach(RequestSets, ParallelOpts, Sub(RequestSet)
Try
DownloadCnt += 1
Dim XmlUrl As String = String.Format("{0}{1}{2}", "http://eutils.ncbi.nlm.nih.gov/entrez/eutils/efetch.fcgi?db=pubmed&id=", String.Join(",", RequestSet), "&retmode=xml&rettype=abstract")
DownloadFile(XmlUrl, String.Format("{0}\TempXML{1}.xml", XMLCacheDir, DownloadCnt))
Catch ex As WebException
Using Response As WebResponse = ex.Response
Dim statCode As Integer = CInt(DirectCast(Response, HttpWebResponse).StatusCode)
MessageBox.Show(String.Format("Failed to retrieve XML due to HTTP error {0}. Please hit the 'Retrieve XML' button to re-run retrieval after the current set is complete.", statCode))
_pool.WaitOne()
Try
If Not File.Exists(RepeatRequestPath) Then
File.WriteAllLines(RepeatRequestPath, RequestSet)
Else
File.AppendAllLines(RepeatRequestPath, RequestSet)
End If
Catch ex as Exception
'Do some error handling here.
Finally
_pool.Release()
End Try
End Using
End Try
End Sub)
svick's solution is almost right. However if you need to protect access to a shared variable you also need to declare your lock object as shared at class level.
This works correctly:
Friend Class SomeClass
Private Shared _lock As New Object
Private Shared sharedInt As Integer = 0
Sub Main()
SyncLock _lock
sharedInt += 1
End SyncLock
End Sub
End Class
If you use a non-shared lock object, synclock will protect the variable only from multiple accessing threads within the same instance, not across instances.