Jason Edel-Brock Jason Edel-Brock - 4 months ago 31
Vb.net Question

VB.Net Threading and Addhandler Troubles

Hello again StackOverflow community!

I am working on a class "SendLogfileClass". In this class I send a logfile via email to said email account. That part works as intended. What I am having problems with is trying to process the Async Completion Event. During said event a Addhandler fires and sets a StatusBar.StatusLabel on the main form.

Here are some relevant chunks of code:

Region "Imports"



Imports System
Imports System.Net
Imports System.Net.Mail
Imports System.Net.Mime
Imports System.Threading
Imports System.ComponentModel
Imports System.IO


End Region



Public Class Form1

Region "Declarations"



End Region



Region "Public"



Private SendmailThread As Thread
Private MailBody As String = Nothing


End Region



Region "Private"



Private mailSent As Boolean = False


End Region



Region "Testing"



End Region



Public Function GetTimestamp() As String

Dim t As Date = Date.Now
Dim timestamp As String = Nothing

Try

timestamp = t.ToLongTimeString & " " & t.ToLongDateString

Catch ex As Exception

Return 1

End Try

Return timestamp

End Function

Private Sub Form1_Load(sender As Object, e As EventArgs) Handles MyBase.Load

Try

If LoggerClock.Enabled = True Then

OutputConsole.Text = "logger Started: " & GetTimestamp() & vbNewLine
OutputConsole.AppendText("Logfile Opened: " & GetTimestamp() & vbNewLine)

StatusLabel.Text = "Logger Status: Active"
StatusBar.Refresh()

Else

OutputConsole.Text = "logger Started: " & GetTimestamp() & vbNewLine

StatusLabel.Text = "Logger Status: Inactive"
StatusBar.Refresh()
End If

SendlogClock.Enabled = True

ToggleViewForm(1)

Catch ex As Exception

Exit Sub

End Try

End Sub

Public Function SetStatus(ByVal [status] As String) As Integer

Try

Thread.Sleep(1000)
StatusLabel.Text = [status]
StatusBar.Refresh()

Catch ex As Exception

Return 1

End Try

Return 0

End Function

Private Sub SendlogThreadTask()

Try

SendLogfile("user@gmail.com", "Logger Logfile", MailBody).ToString()

Catch ex As Exception

Exit Sub

End Try

End Sub

Private Sub SendlogClock_Tick(sender As Object, e As EventArgs) Handles SendlogClock.Tick

Try

OutputConsole.AppendText("Logfile Closed: " & GetTimestamp() & vbNewLine)

SendmailThread = New Thread(AddressOf SendlogThreadTask)
SendmailThread.IsBackground = True
SendmailThread.Start()

OutputConsole.ResetText()
OutputConsole.Text = "Logfile Opened: " & GetTimestamp() & vbNewLine

Catch ex As Exception

Exit Sub

End Try

End Sub

Public Sub SendCompletedCallback(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)

Try

' Get the unique identifier for this asynchronous operation.
Dim token As String = CStr(e.UserState)

If e.Cancelled Then

StatusLabel.Text = "Send Canceled... " & token
StatusBar.Refresh()

End If

If e.Error IsNot Nothing Then

StatusLabel.Text = "Error: " & token & " " & e.Error.ToString() & " "
StatusBar.Refresh()

Else

StatusLabel.Text = "Message Sent... "
StatusBar.Refresh()

End If

mailSent = True

Catch ex As Exception

Exit Sub

End Try

End Sub

Public Function SendLogfile(ByVal mailTo As String, ByVal mailSubject As String, ByVal mailBody As String, Optional ByVal doAttach As Boolean = False, Optional ByVal messageAttach As String = Nothing) As Integer

Try

' SMTP Server
Dim SmtpServer As String = "mail.domain.com"

' Command line argument must the the SMTP host.
Dim Cli As New SmtpClient(SmtpServer)

' Specify the e-mail sender.
' Create a mailing address that includes a UTF8 character
' in the display name.
Dim [from] As New MailAddress("logger@domain.com", "logger " & ChrW(&HD8) & " logs", System.Text.Encoding.UTF8)

' Set destinations for the e-mail message.
Dim [to] As New MailAddress(mailTo)

' Specify the message content.
Dim message As New MailMessage([from], [to])

message.Body = mailBody

' Include some non-ASCII characters in body and subject.
Dim someArrows As New String(New Char() {ChrW(&H2190), ChrW(&H2191), ChrW(&H2192), ChrW(&H2193)})

message.Body += Environment.NewLine & someArrows
message.BodyEncoding = System.Text.Encoding.UTF8
message.Subject = mailSubject & someArrows
message.SubjectEncoding = System.Text.Encoding.UTF8

' Put the mail attachment in a list of items
'Dim attachment As New Attachment(messageAttach)

' Attach file.
'If doAttach = True Then

'If File.Exists(messageAttach) Then

'message.Attachments.Add(attachment)

'End If

'End If

' Set the method that is called back when the send operation ends.
AddHandler Cli.SendCompleted, AddressOf SendCompletedCallback

' The userState can be any object that allows your callback
' method to identify this send operation.
' For this example, the userToken is a string constant.
Dim userState As String = "OK"

Cli.SendAsync(message, userState)

'MsgBox("Sending message... press c to cancel mail. Press any other key to exit.")

Dim answer As String = "OK" ' or CANCEL

' If the user canceled the send, and mail hasn't been sent yet,
' then cancel the pending operation.
If answer.StartsWith("C") AndAlso mailSent = False Then

Cli.SendAsyncCancel()

End If

' Clean up.
message.Dispose()

Catch ex As Exception

MsgBox("Encountered Error: " & vbNewLine & vbNewLine & ex.ToString())
Return 1

End Try

Return 0

End Function


End Class

Answer

Your event handler is executed on a secondary thread and in that event handler you are referring to the default instance of MainForm. Default instances are thread-specific so that is a different form object to the one you're looking at on-screen.

You can generally use the SynchronizationContext class to enable marshalling a method call to the UI thread but that's not possible in your case because you're actually creating the object on a secondary thread too. In that case, you'll have to pass a reference to the existing MainForm object into that mail sender and use that to marshal a method call to the UI thread using its InvokeRequired and Invoke/BeginInvoke members.