I'm using the following code to send an email asynchronously. It's usually through gmail. It works fine but occasionally there is a collision with an IMAP check of the email account or a follow up SMTP async send. The attacments can get to 10-25 mbs which takes a minute or two to upload. I'm trying to prevent those collisions by incorporating a global variable 'MailSendInProgress' It gets set to TRUE just before the asyncsend and to FALSE in the callback from the async. I also use it as a check during some IMAP operations elsewhere in the application as this is what seemed to interfere and cause an error in the async email sending process. My question is whether there is a better way to accomplish this. This process seems to work OK but it feels hokey and maybe will cause problems in the future? Thanks for any thoughts you might have!
Check to prevent collisions from IMAP or other SMTP send operations:
If MailSendInprogress Then Exit Sub
Public MailSendInprogress As Boolean = Falsepublic sub SendMail() Try Dim SmtpServer As New SmtpClient() AddHandler SmtpServer.SendCompleted, AddressOf SendCompletedCallback Dim mail As New MailMessage() SmtpServer.Credentials = New Net.NetworkCredential(strMailLogin, strMailPassword) SmtpServer.Port = 587 SmtpServer.EnableSsl = True SmtpServer.Host = strSMTPServer mail = New MailMessage() mail.From = New MailAddress(strMailLogin) If strAttachments = "YES" Then Dim attachment As System.Net.Mail.Attachment Using attachment attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) End Using End If mail.To.Add(strTo) mail.Subject = strSubject mail.Body = strBody If IsHTML Then mail.IsBodyHtml = True Do Until MailSendInprogress = False Thread.Sleep(1000) Loop MailSendInprogress = True SmtpServer.SendAsync(mail, Nothing) LogIt("**** MAIL SENT *****") Catch ex As Exception LogIt("ERROR Sending email!" & ex.StackTrace) End Try Private Sub SendCompletedCallback(sender As Object, e As AsyncCompletedEventArgs) ' Throw New NotImplementedException Try If e.Error IsNot Nothing Then LogIt("ERROR SENDING ASYNC EMAIL: " & e.Error.ToString) Else LogIt("ASYNC EMAIL SEND COMPLETED.") End If Catch ex As Exception LogIt("ERROR " & ex.ToString) End Try MailSendInprogress = False End Sub
If you are concerned about multiple hits to same process or method, then you can use lock statement. It is reffered as SyncLock in VB.net. Furthermore you may not need MailSendInprogress to track status of SendMail.
Public MailSendInprogress As Boolean = Falsepublic sub SendMail() Try SyncLock Me' Or MailSendInprogress Or any global or static/shared variable Dim SmtpServer As New SmtpClient() AddHandler SmtpServer.SendCompleted, AddressOf SendCompletedCallback Dim mail As New MailMessage() SmtpServer.Credentials = New Net.NetworkCredential(strMailLogin, strMailPassword) SmtpServer.Port = 587 SmtpServer.EnableSsl = True SmtpServer.Host = strSMTPServer mail = New MailMessage() mail.From = New MailAddress(strMailLogin) If strAttachments = "YES" Then Dim attachment As System.Net.Mail.Attachment Using attachment attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) End Using End If mail.To.Add(strTo) mail.Subject = strSubject mail.Body = strBody If IsHTML Then mail.IsBodyHtml = True Do Until MailSendInprogress = False Thread.Sleep(1000) Loop MailSendInprogress = True SmtpServer.SendAsync(mail, Nothing) LogIt("**** MAIL SENT *****") Catch ex As Exception LogIt("ERROR Sending email!" & ex.StackTrace) End TryEnd SyncLock
Thanks for your comment. I spent some time checking out synclock and it doesn't seem like it's going to be appropriate for my code. What I would like to do is the prototypical long email send process in the background while the GUI remains responsive. I thought the asyncsend would be the answer but it isn't reliable. I thought it was due to collisions of checking the same email account with IMAP in one code block and sending an email with SMTP using the same email account. Now I'm not so sure. On my development computer the below code worked flawlessly for several days. When I installed it on a test computer it only works intermittently. It will send a few emails then putter out after an hour or two at the most. SOME emails will send after that but the majority result in an sendasync error captured in the callback. Both computers have .Net 4, WIndows 7. One is on a wireless network the other is cat6. The test computer has several other network processes running that are reliable using a synchronous mail send process. COuld that be the interference? The error information returned on the call back of sendasync is limited:
System.Net.Mail.SmtpException: Failure sending mail. ---> System.Net.Sockets.SocketException: An invalid argument was supplied
at System.Net.Mail.SmtpConnection.ConnectAndHandshakeAsyncResult.End(IAsyncResult result)
at System.Net.Mail.SmtpClient.ConnectCallback(IAsyncResult result)
--- End of inner exception stack trace ---INNER EXCEPTION: System.Net.Sockets.SocketException (0x80004005): An invalid argument was supplied
at System.Net.Mail.SmtpConnection.ConnectAndHandshakeAsyncResult.End(IAsyncResult result)
at System.Net.Mail.SmtpClient.ConnectCallback(IAsyncResult result)
Here's the code I'm currently using:
Public MailSendInprogress As Boolean = False Dim mail As New MailMessage() Dim SmtpServer As New SmtpClient() Public Sub SendMail(ByVal strFrom As String, ByVal strTo As String, _ ByVal strCC As String, ByVal strSubject As String, ByVal strBody As String, ByVal strAttachments As String, ByVal strSMTPServer As String, _ ByVal strMailLogin As String, ByVal strMailPassword As String, IsHTML As Boolean) Try SmtpServer = New SmtpClient() AddHandler SmtpServer.SendCompleted, AddressOf SendCompletedCallback SmtpServer.Credentials = New Net.NetworkCredential(strMailLogin, strMailPassword) SmtpServer.Port = 587 SmtpServer.EnableSsl = True SmtpServer.Host = strSMTPServer mail = New MailMessage() mail.From = New MailAddress(strMailLogin) If strAttachments = "YES" Then Dim attachment As System.Net.Mail.Attachment Using attachment attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) attachment = New System.Net.Mail.Attachment(Application.StartupPath & "\") mail.Attachments.Add(attachment) End Using End If mail.To.Add(strTo) mail.Subject = strSubject mail.Body = strBody If IsHTML Then mail.IsBodyHtml = True MailSendInprogress = True SmtpServer.SendAsync(mail, Nothing) LogIt("**** ATTEMPTING TO SEND MAIL *****") Catch ex As Exception SmtpServer.Dispose() mail.Dispose() LogIt("ERROR Sending email!" & ex.StackTrace) End Try End Sub Private Sub SendCompletedCallback(sender As Object, e As AsyncCompletedEventArgs) ' Throw New NotImplementedException Try If e.Error IsNot Nothing Then LogIt("ERROR SENDING ASYNC EMAIL: " & e.Error.ToString & "INNER EXCEPTION: " & e.Error.InnerException.ToString()) Else LogIt("ASYNC EMAIL SEND COMPLETED.") End If Catch ex As Exception LogIt("ERROR " & ex.ToString) End Try MailSendInprogress = False SmtpServer.Dispose() mail.Dispose() End Sub
If you are concerned about multiple hits to same process or method, then you can use lock statement. It is reffered as SyncLock in VB.net. Furthermore you may not need MailSendInprogress to track status of SendMail.
You may find following articles helpful:
https://msdn.microsoft.com/en-us/library/3a86s51t.aspx
http://www.dotnetperls.com/synclock
Open in new window