Catch exception when using async/await and Task in VB.Net - vb.net

I am parsing email from Outlook from vb.net. The main form displays the total count of mail for the selected Outlook folder and allows me to override this number so that I can process 1, 10, or all. When I click a button I open a new form passing in ref to my outlook mailClient class to provide access to the mail items. The form new and show events set up things and within a try/catch make a call to an async function like so.
Try
AllEMails = Await ScanMailItems(ref_clsEmailClient.ECFMailToScan,progressIndicator, tokenSource.Token)
Catch ag As AggregateException
If ag.InnerExceptions IsNot Nothing Then
MsgBox(ag.InnerExceptions.Count.ToString)
For Each ex In ag.InnerExceptions
MsgBox(ex.Message)
Next
End If
Catch ex As Exception
MsgBox(ex.Message & vbNewLine & vbNewLine & _
ex.StackTrace)
End Try
In this ScanMailItems() I am setting up a For Each Next to loop the mail items like so.
Dim EmailsFraction As Integer = Await Task(Of Integer).Run(
Function()
For i = ECFMailItems.Count To 1 Step -1
'other code
Throw New Exception("Test Error 1")
UpdateFormDelegate()
counter += 1
Next
Return counter
End Function)
Return EmailsFraction
The code this far is working and in the loop I am updating controls with some delegate subs to show subject and the email body of each email that will be parsed in the loop. My trouble is that I have placed some Throw New Exception("Test Error") in the for loop and in the delegate subs to be sure they will propagate back to my try/catch in my shown event. They do not and I have tried AggregateException. I can post actual code but think it is all in how the exception propagates through the async task.
I am just learning to do this, is there a better way to set it up? The main thing is that on each iteration of the For Loop my progress bar and the other controls need to update and I will be calling into other classes.

Related

This method can't be used with an inline response mail item

I have an Outlook add-in that monitors sent items and moves specific emails to a folder. I am not sure what have changed, but when I send an email, I get this error:
This method can't be used with an inline response mail item.
Here is my code. It fails while moving the mail item (Mail.Move(TargetFolder)) when IsSentItem is true:
Private Sub MoveMailToFolder(ByVal TargetFolder As Outlook.Folder, ByVal Mail As Outlook.MailItem, ByVal IsSentItem As Boolean)
Try
If Mail.ReadReceiptRequested Then
''continue /keep original mail as is
Else
'Mail.UnRead = False
Mail.UnRead = True
End If
If IsSentItem Then
Mail.UnRead = False
Mail.Move(TargetFolder)
Else
Mail.Move(TargetFolder)
'Mail.SaveSentMessageFolder = TargetFolder
End If
Catch ex As Exception
MsgBox(ex.Message.ToString & " " & ex.HResult.ToString & " " & ex.GetBaseException.ToString)
Finally
''
End Try
End Sub
Can you advise how this can be fixed?
Yes, the inline response needs to be closed first. You also need to avoid calling methods like that from inline response or MailItem event handlers. In the latter case, you can start a timer (use the Timer class from the Forms namespace rather than Threading as it fires on the main thread) and call code like yours in the timer event handler (when you are out of the MailItem event handler).
To close an inline response, you can try to use Accessibility API to simulate a click on the "Discard" button or, if using Redemption (I am its author) is an option, its SafeExplorer object - it exposes ActiveInlineResponseDiscard method:
set sExplorer = CreateObject("Redemption.SafeExplorer")
sExplorer.Item = Application.ActiveExplorer
sExplorer.ActiveInlineResponseDiscard

Cannot mark Excel workbook as Final

The situation is as follows:
We want to publish to a remote machine a locally edited file. This file could be of type Word, Excel, Powerpoint. Apparently, after the publishing to the remote machine, we would like the local document to be marked as final, in order to prevent the user from editing it again (, because the intented workflow is first downloading it from the remote server, editing the downloaded document and the publishing it back to the server).
So, there is a bunch of code like this:
Public Sub setDocFinal()
Select Case addin.HostType
Case ADXOfficeHostApp.ohaWord
Dim doc As Word.Document = Nothing
Try
doc = addin.WordApp.ActiveDocument
doc.Final = True
Catch ex As Exception
Throw New Exception(Me.addin.getLabel("cannotSaveCopy", "Cannot Save the document."))
Finally
Marshal.ReleaseComObject(doc)
End Try
Case ADXOfficeHostApp.ohaExcel
Dim doc As Excel.Workbook = Nothing
Try
doc = addin.ExcelApp.ActiveWorkbook
doc.Final = True
'doc.RefreshAll()
'doc.CalculateUntilAsyncQueriesDone()
'doc.Calculate()
Catch ex As Exception
Throw New Exception(Me.addin.getLabel("cannotSaveCopy", "Cannot Save the document."))
Finally
Marshal.ReleaseComObject(doc)
End Try
' Powerpoint case intentionally skipped as is has same format/code
End Select
End Sub
The above code works pretty well for the Word case, but when it comes to Excel, it stacks on the popup which informs the user for the publish action to the remote server:
EDIT
At that particular point, the execution freezes (or maybe gets into an infinite internal loop, because the only available buttons in debugging mode are pause and stop) at the line of setting the document as final and it never reaches the finally statement (where we release the object). It also seems like the execution tries to return control back to the excel document, but nothing more than this notification occurs :
Any idea of what is wrong in the above code, regarding the handling of Excel?
The lines in comments display some trials I have been going through, while trying to find a solution around the net.
In addition, here is also the popup's related code:
Private Sub doWork(ByVal action As String, ByVal e As System.ComponentModel.DoWorkEventArgs)
Dim myDoc As MyDocument
myDoc = DirectCast(e.Argument, MyDocument)
System.Diagnostics.Debug.WriteLine("Post in Thread")
Dim resultObject(3) As Object
resultObject(0) = True
resultObject(1) = myDoc
Dim saveDocumentResult As SaveDocumentResult = Nothing
Try
Select Case action
Case Constants.SAVE_DRAFT
saveDocumentResult = myDoc.getRequestService().saveDraft(myDoc.getSaveFile(), myDoc.getDocName(), myDoc)
Case Constants.PUBLISH
saveDocumentResult = myDoc.getRequestService().publishDocument(myDoc.getSaveFile(), myDoc.getDocName(), myDoc)
myDoc.setDocFinal() 'this line makes the call to the above code
End Select
myDoc.updateDocumentProperty(Constants.VERSION, saveDocumentResult.version)
myDoc.updateDocumentProperty(Constants.HASH, saveDocumentResult.hash)
myDoc.setSaved(True)
System.Threading.Thread.Sleep(1500)
Catch ex As Exception
resultObject(0) = False
resultObject(2) = ex.Message
Finally
e.Result = resultObject
End Try
End Sub

Looping through a form's controls; can this be more efficient?

I'm beginning to create a class to easily style my Windows apps. The concept is to just add the reference to the class on Form Load, then have the class automatically style each control on the form according to the properties I have for each control in the style class.
I've created a method where I just pass the Form to it, then loop through each control, check which type it is, then style it accordingly. No problem there so far; the problem is, if the form has a container control on it (i.e. a GroupBox), the items in it also need to be looped through, and checked for matches, and styled accordingly. So, if for example, I already have a check for a Button control in the main loop, with all of its style code there, then if the GroupBox has a button within it, it goes unstyled, unless I put the exact same check and style code in that loop as well ...
As one can imagine, checking for every single type of control + adding it's style code to each, and then repeating the exact same thing within another loop inside that loop, just to add all the same checks and style code, would turn into a TON of code (not to mention duplicated lines).
I'm wondering if maybe there's a smarter way of doing this, other than moving all of the style code into their own Subs, per control, then duplicating all of the same checks in both loops, and just calling each Sub for the styling? (Which is still messy, but at least all of that control style code isn't duplicated).
Here's the basic code I have right now; for the sake of clarity, I omitted all of the actual styling code per control (where you see the ' Style the control comments in each):
Public Sub MyStyle(ByVal zForm As Form, Optional lstIgnoreControl As List(Of Control) = Nothing)
Try
' Form
With (zForm)
' Style the basic Form properties
End With
Catch ex As Exception
End Try
For Each zCntl As Control In zForm.Controls
' Controls
If zCntl.HasChildren Then
For Each zChildCntl As Control In zCntl.Controls
If zChildCntl.GetType Is GetType(CheckBox) Then
' Style the Checkbox
End If
Next
End If
If IsNothing(lstIgnoreControl) = True OrElse lstIgnoreControl.Contains(zCntl) = False Then
Select Case zCntl.GetType
Case GetType(MenuStrip)
Try
' Style the menustrip
Catch ex As Exception
End Try
Case GetType(StatusStrip)
Try
' Style the Statusstrip
Catch ex As Exception
End Try
Case GetType(Label)
Try
' Style the Label
Catch ex As Exception
End Try
Case GetType(LinkLabel)
Try
' Style the LinkLabel
Catch ex As Exception
End Try
Case GetType(Panel)
Try
' Style the Panel
Catch ex As Exception
End Try
Case GetType(Button)
Try
' Style the Button
Catch ex As Exception
End Try
Case GetType(PictureBox)
Try
' Style the PictureBox
Catch ex As Exception
End Try
Case GetType(DataGridView)
Try
' Style the DataGridView
Catch ex As Exception
End Try
Case GetType(GroupBox)
Try
' Style the GroupBox
Catch ex As Exception
End Try
Case GetType(DateTimePicker)
Try
' Style the DateTimePicker
Catch ex As Exception
End Try
End Select
End If
Next
End Sub
The zCntl.HasChildren loop is the sub loop I'm referring to, where the redundency would all come in, that I'm hoping to avoid.
tl;dr but
Iterator Function allControls(c As Control) As IEnumerable(Of Control)
Yield c
For Each cc As Control In c.Controls
For Each ccc In allControls(cc)
Yield ccc
Next
Next
End Function
sample use:
For Each gb In allControls(zForm).OfType(Of GroupBox)
' ...
Next
or better put them all in a LookUp:
Dim types = allControls(zForm).ToLookup(Function(c) c.GetType)
For Each gb In types(GetType(GroupBox))
' ...
Next

Setting a textbox property value within a catch block

Using VS 2013 VB.net for my ClickOnce application. I've got a function which verifies database functionality and the guts are wrapped in a Try Catch. A portion of my Catch block looks like this:
Catch ex As Exception When Err.Number = "5"
My.Application.Log.WriteException(ex)
If My.Settings.g_blnDebugMode Then
MessageBox.Show(Err.Number & " " & ex.ToString, "Exception Error")
End If
If Err.Description.Contains("The specified table does not exist") Then
MessageBox.Show("Selected file is not a valid database.", "Exception Error")
ElseIf Err.Description.Contains("The specified password does not match the database password.") Then
MessageBox.Show("The specified password does not match the current database password.", "Exception Error")
End If
Return False
What I want to do is, clear two different fields based on the two custom error messages at the bottom. Something like TextBox1.Text = "" or TextBox2.Text = "" depending on which error is thrown (invalid password or invalid database). My problem is that I don't seem to be able to set them directly or set the value of a module or global variable from within the catch block.
Error is:
Cannot refer to an instance member of a class from within a shared method or shared member initializer without an explicit instance of the class.
If it's possible how can I work around this and set my TextBoxes based on the results in the Catch block?
Usually the method to achieve what you are trying is to use a second a catch block
Catch ex As Exception When Err.Number = ""
MessageBox.Show(ex.Message)
FlushTextBox1();
Catch ex As Exception When Err.Number = ""
MessageBox.Show(ex.Message)
FlushTextBox2();
The error is likely appearing because the try-catch block is inside a Shared method. What that means is, the change in the value of TextBox will be repeated for all instances of the class. If you want the TextBox to behave like this, add the Shared keyword to its own declaration and remove it from the Sub's declaration. If you don't want this behaviour at all, just remove the Shared keyword. For more information on the error check the MSDN article
Alternatively you can call a local function (as shown in code) FlushTextBox1() to change the value of the TextBox outside the Sub.

Updating Variable in Multithreading in VB.NET

I've wrote a program which on startup loads the computer list from Active Directory. This takes about 10 seconds. If the user has started the program with a specific host as parameter, it should be usable immediately.
So to don't interrupt the user I want to load the computer list in a different thread. The problem is that it writes to a variable (the computer list) which is also used in the main thread.
You may think, I could simply use a temporary variable and when its done overwrite the main variable. But I have to keep existing data of the main variable.
'hosts list
Private Shared hosts As New SortedDictionary(Of String, HostEntry)
'Get all computers in Active Directory
'Will run in a extra thread
Private Delegate Sub GetADcomputersDelegate()
Private Sub GetADcomputers()
If Me.InvokeRequired Then
Me.Invoke(New GetADcomputersDelegate(AddressOf GetADcomputers), Nothing)
Else
lblStatusAD.Text = "Getting Computers..."
Try
Dim search As New DirectorySearcher(ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry(), "(objectClass=computer)")
For Each host As SearchResult In search.FindAll()
'AddHost creates a new HostEntry object and adds it to my "global" hosts variable
'It also checks if a host is already present in the list and only updates it.
AddHost(host.GetDirectoryEntry().Properties("cn").Value.ToLower(), host.GetDirectoryEntry().Properties("description").Value)
Next
Catch ex As Exception
Debug.WriteLine("GetADcomputers() Exception: " & ex.Message)
End Try
ThreadPool.SetMaxThreads(hosts.Count, hosts.Count)
Dim ah As String = activehost
'Fill my ListBox with the computers
lstHosts.DataSource = New BindingSource(hosts, Nothing)
'Select the computer that was selected before
UseHost(ah)
lblStatusAD.Text = ""
End If
End Sub
So when GetADcomputers() runs in its own thread, the main thread is also blocked. I guess because auf the hosts variable.
So what could I change to make the thread do it's work and after that apply the updated computer list without losing data of entries in old hosts list? And all this in a fast and efficient way.
That code is very wrong. If you call that method on a secondary thread then it immediately marshals a call back to the UI thread and does EVERYTHING on the UI thread. What you should be doing is executing all the background work on the secondary thread and then marshalling to the UI thread ONLY to update the UI.
Get rid of that If...Else block and just make the entire body of the method what's current ly in the Else block. Next, identify all the lines that specifically interact with the UI and remove each of those to their own method. You then add If...Else blocks to each of those methods so that only the code that actually touches the UI is executed on the UI thread.
Here's a start:
Private Sub GetADcomputers()
UpdateStatusADLabel("Getting Computers...")
Try
Dim search As New DirectorySearcher(ActiveDirectory.Domain.GetCurrentDomain().GetDirectoryEntry(), "(objectClass=computer)")
For Each host As SearchResult In search.FindAll()
'AddHost creates a new HostEntry object and adds it to my "global" hosts variable
'It also checks if a host is already present in the list and only updates it.
AddHost(host.GetDirectoryEntry().Properties("cn").Value.ToLower(), host.GetDirectoryEntry().Properties("description").Value)
Next
Catch ex As Exception
Debug.WriteLine("GetADcomputers() Exception: " & ex.Message)
End Try
ThreadPool.SetMaxThreads(hosts.Count, hosts.Count)
Dim ah As String = activehost
'Fill my ListBox with the computers
lstHosts.DataSource = New BindingSource(hosts, Nothing)
'Select the computer that was selected before
UseHost(ah)
lblStatusAD.Text = ""
End Sub
Private Sub UpdateStatusADLabel(text As String)
If lblStatusAD.InvokeRequired Then
lblStatusAD.Invoke(New Action(Of String)(AddressOf UpdateStatusADLabel), text)
Else
lblStatusAD.Text = text
End If
End Sub