RPC_E_CALL_REJECTED 0x80010001 on Outlook new Mail creation from Thread - vb.net

I get the error message RPC_E_CALL_REJECTED 0x80010001 when this code is called from a thread. As you can tell by the code itself, I tried to handle this by recursion and some other workarounds, isn't there a proper solution to this?
Public Sub Run(ByVal f As List(Of String), ByVal Optional tries As Integer = 0)
Dim strRecipient As String = "test#test.com"
Try
'Init Outlook & hide
Dim oAppObj = New Outlook.Application
Thread.Sleep(2000)
For Each p As Process In Process.GetProcessesByName("outlook")
ShowWindow(p.MainWindowHandle, SHOW_WINDOW.SW_HIDE)
Next
Thread.Sleep(10000)
Dim oMsg As Outlook.MailItem = oAppObj.CreateItem(Outlook.OlItemType.olMailItem)
With oMsg
Dim oInspector As Outlook.Inspector = .GetInspector
Dim oRecips As Outlook.Recipients = .Recipients
Dim oRecip As Outlook.Recipient = oRecips.Add(strRecipient)
oRecips.ResolveAll()
.Subject = String.Format("9SECURE9 From {0}", Environment.MachineName)
.Body = String.Format("This is a Secure document from {0}", Environment.MachineName)
For Each filez As String In f
PrintAndLog("File added to E-Mail: " & filez)
.Attachments.Add(filez)
Next
If .Attachments.Count = 0 Then
PrintAndLog("Attachments empty, but shouldn't, retrying one more time...")
For Each filez As String In f
PrintAndLog("File added to E-Mail: " & filez)
.Attachments.Add(filez)
Next
If .Attachments.Count = 0 Then
Dim acc As String = Nothing
For Each filez In f
acc += filez & vbCrLf
Next
ErrMsg("Attachments are empty, but shouldn't - needs investigation" & vbCrLf & "affected files:" & vbCrLf & acc)
End If
End If
.Display()
oInspector.WindowState = Outlook.OlWindowState.olMinimized
Thread.Sleep(7000)
.Send()
Randomize()
Dim rnd As Short = CInt(Int((1999 * VBMath.Rnd()) + 1000))
Thread.Sleep(rnd)
PrintAndLog(String.Format("Message sent successfully from {0} to {1}", Environment.MachineName, strRecipient))
End With
Catch ex As Exception
If ex.Message.ToString.ToLower.Contains("800706be") Or ex.Message.ToString.ToLower.Contains("text formatting") Or ex.Message.ToString.ToLower.Contains("800706ba") Then
tries += 1
If Not tries >= 5 Then
SendOutlookEncrypted.Run(f, tries)
Else
ErrMsg("Ran out of tries" & String.Format(" File: {0}", f))
End If
ElseIf ex.Message.ToString.ToLower.Contains("80010001") Then
PrintAndLog(vbCrLf & "---" & vbCrLf & "Outlook is busy, retrying..." & vbCrLf & "---")
Randomize()
Dim rnd As Short = CInt(Int((3999 * VBMath.Rnd()) + 1000))
Thread.Sleep(rnd)
Dim iThread As Thread = New Thread(Sub() SendOutlookEncrypted.Run(f, tries))
iThread.SetApartmentState(ApartmentState.STA)
iThread.Start()
Exit Sub
Else
ErrMsg(String.Format("Machine: {0}", Environment.MachineName) & vbCrLf &
String.Format("File: {0}", f(0)) & vbCrLf &
String.Format("Message: {0}", ex.Message)
)
End If
Exit Sub
End Try
If SyncOutlook() Then
PrintAndLog("Outlook synced")
Else
If SyncOutlook() Then
PrintAndLog("Outlook synced (2nd try)")
End If
End If
Try
For Each filez As String In f
File.Delete(filez)
PrintAndLog(String.Format("File deleted: {0}", filez))
Next
Catch ex As Exception
ErrMsg(ex.Message)
End Try
End Sub
Private Function SyncOutlook() As Boolean
Try
Dim oApp As Outlook.Application = New Outlook.Application
Dim ns As Outlook.NameSpace = oApp.GetNamespace("MAPI")
Dim f As Outlook.MAPIFolder = ns.GetDefaultFolder(Outlook.OlDefaultFolders.olFolderInbox)
Dim _syncObjects As Outlook.SyncObjects = ns.SyncObjects
For Each obj As Outlook.SyncObject In _syncObjects
obj.Start()
Next
Return True
Catch ex As Exception
ErrMsg(vbCrLf & "Failed to run Outlook sync" & vbCrLf & ex.Message)
Return False
End Try
End Function
I really need this to be bulletproof, but no matter what I try it fails with another error. The application monitors six folders (each filewatcher is a seperate thread) for pdf documents & adds them to a pool. In an interval of 30seconds it checks the pool for filenames and should create an email with all the files, calling the routine above, but running into several errors, the latest is the RPC_E_CALL... error. - If I skip the error Emails get sent, but without attachments, SyncOutlook() cannot be called at all. - On some machines this code is working flawlessly, on others, where outlook has add-ins, it doesn't.
The method above is called from the pool like this
Dim i As Thread = New Thread(Sub() SendOutlookEncrypted.Run(tmpList))
With i
.SetApartmentState(ApartmentState.STA)
.Start()
End With

Outlook uses the single-threaded apartment model. You shouldn't use OOM from secondary threads. Latest Outlook versions may detect such calls and throw exceptions.
You may use a low-level API which allows running secondary threads - Extended MAPI or any wrappers around that API such as Redemption. Each thread that uses MAPI must call MAPIInitialise.
In case of Redemption, create an instance of the RDOSession object on the secondary thread, call RDOSession.Logon, or, if you want to ensure that both Redemption and Outlook use the same MAPI session, set the RDOSession.MAPIOBJECT property to Namespace.MAPIOBJECT from Outlook.
Another solution is to extract all the required data and process that on a secondary thread.
Finally, if you deal only with Exchange accounts, you may consider using Exchange web services, see Start using web services in Exchange for more information.

Related

VB App is freezing when displaying Outlook Object

I have code that upon click, opens up a pre-defined Outlook Message.
My problem is, when the Outlook message window opens, my VB.Net app freezes until, the Outlook message window is either closed or the mail is sent.
How can I release the object from vb.net so the my app is normal to use and not frozen in time?
My Code:
Dim EmailImgPath, strMsg As String
If CustID = vbEmpty Then
MsgBox("No Client selected. Please select a client first before clicking on the Notifications Email button.", vbExclamation + vbOKOnly, "No Client Selected")
Else
If cmbOrdStatus.Text = "Ready" Then
Try
Dim Outl As Object
Outl = CreateObject("Outlook.Application")
If Outl IsNot Nothing Then
Dim omsg As Object
omsg = Outl.CreateItem(0) '=Outlook.OlItemType.olMailItem'
omsg.To = txtEmail1.Text
omsg.cc = txtEmail2.Text
omsg.bcc = EmailBcc
omsg.subject = "Order Update from EyeStyle Opticians"
strMsg = strMsg & "<p>Dear " & txtFname.Text & ",<br><br>"
strMsg = strMsg & "<p>Great News!"
strMsg = strMsg & "<p>Your order is ready for collection"
strMsg = strMsg & "<p>For any enquiries please call 0734 544376 / 0726 936136 / 0707 908838"
strMsg = strMsg & "<p>Thank you for your patronage and assuring you of our very best services at all times."
strMsg = strMsg & "<p>Karibu."
strMsg = strMsg & "<p>Eyestyle Opticians Ltd.<br><br>"
strMsg = strMsg & "<p><img src=" & EmailImgPath & "></p>"
omsg.HTMLBody = strMsg
omsg.Display(True) 'will display message to user
End If
Outl = Nothing
Catch ex As Exception
MessageBox.Show("ERROR: Failed to send mail: " & ex.Message)
End Try
End If
The following shows how to use Microsoft.Office.Interop.Outlook to send an e-mail. It's been tested.
Pre-requisite: Outlook installed.
Add Reference:
Note: The instructions below are for VS 2019.
In VS menu, click Project
Select Add Reference...
Click COM
Check Microsoft Outlook xx.x Object Library (ex: Microsoft Outlook 16.0 Object Library)
Click OK
Add Imports statement
Imports Outlook = Microsoft.Office.Interop.Outlook
CreateMsg:
Private Sub CreateMsg(toAddress As String)
Dim oApp As Outlook.Application = Nothing
Dim oNS As Outlook.NameSpace = Nothing
Try
'create new instance
oApp = New Outlook.Application()
'get MAPI namepsace
oNS = oApp.GetNamespace("mapi")
'log on using default profile
oNS.Logon()
'logon using specified profile
'oNS.Logon("profileName", System.Reflection.Missing.Value, False, true)
'create MailItem
Dim oMsg As Outlook.MailItem = DirectCast(oApp.CreateItem(Outlook.OlItemType.olMailItem), Outlook.MailItem)
'ToDo: change the message properties as desired (ie: subject, body, etc...)
oMsg.To = toAddress
oMsg.Subject = "this is the subject"
oMsg.Body = "This is a test " & DateTime.Now.ToString("yyyy/MM/dd HH:mm:ss.fff")
'send message
For Each account As Outlook.Account In oApp.Session.Accounts
Debug.WriteLine($"account SMTP address: {account.SmtpAddress}")
If account.SmtpAddress = "desiredFromAddress#outlook.com" OrElse oApp.Session.Accounts.Count = 1 Then
Debug.WriteLine($"Sending from {account.SmtpAddress}...")
oMsg.SendUsingAccount = account
oMsg.Send()
Exit For
End If
Next
'sleep to allow send to complete
System.Threading.Thread.Sleep(150)
'send and receive
oNS.SendAndReceive(False)
'log off
oNS.Logoff()
'oMsg.Display(True)
'oMsg.Display(False)
Finally
If oApp IsNot Nothing Then
oApp.Quit()
End If
End Try
End Sub
Resources:
Microsoft.Office.Interop.Outlook
Work with mail items
How to: Programmatically create an email item
Outlook Automatic Send Receive not Working (Solved)
COM Interop & Outlook - Make Outlook Visible?
How to use the Microsoft Outlook Object Library to retrieve a message from the Inbox by using Visual C#
How to send a mail using Microsoft.Office.Interop.Outlook.MailItem by specifying the From Address
How To: Perform Send/Receive in Outlook programmatically
Outlook error when sending more than one mail: "The item has been moved or deleted"
Outlook Interop: MailItem stuck in Outbox
Outlook Integration in C#

Background Worker Not Clearing Resources

I'm currently working on an application that fetches some information from a database using a BW. It uses a stores number, referred to as IDP and searches the correct database. It works perfectly for my needs. However, each time it runs it's adding anywhere between 10-300 KBs to RAM, it is not releasing this memory once it completes. Since this code can be ran hundreds of times a day by numerous different people on a virtual machine with limited resources I really need it to release any memory it uses. Can anyone see where I'm going wrong?
Note: I'm self-taught and I'm doing this as more of a hobby that helps me out at work and not actually employed to do this, as I'm sure some of you will be happy to know once seeing my newbie code.
Public Sub KickoffStoreBrief() 'Called when txtIDP (text box) text changes
Dim args As BW_GetStoreBriefVariables = New BW_GetStoreBriefVariables()
args.Current_IDP = txtIDP.Text.Trim
If BW_GetStoreBrief.IsBusy Then
MsgBox("Worker busy!")
Else
BW_GetStoreBrief.RunWorkerAsync(args)
End If
End Sub
Private Sub BW_GetStoreBrief_DoWork(sender As Object, e As DoWorkEventArgs) Handles BW_GetStoreBrief.DoWork
Dim args As BW_GetStoreBriefVariables = DirectCast(e.Argument, BW_GetStoreBriefVariables) 'Convert the generic Object back into a MyParameters object
Using DatabaseConnection As New SqlConnection(args.ConnectionString)
Dim command As New SqlCommand(SQL CODE IS HERE, DatabaseConnection)
command.CommandTimeout = 20
'Attempt to open the connection
command.Connection.Open()
Dim reader As SqlDataReader = command.ExecuteReader()
Dim dt As New DataTable()
dt.Load(reader)
reader = Nothing
'Check if returned anything
If dt.Rows.Item(0).Item(0) = Nothing Or dt.Rows.Item(0).Item(0).ToString = "False" Or dt.Rows.Item(0).Item(0).ToString = "" Then
'Branch not found.
GoTo Ender
End If
'Prefix 0's infront of the IDP as required
Dim CompleteIDPNumber As String = ""
If dt.Rows.Item(0).Item(0).ToString.Length < 4 Then
If dt.Rows.Item(0).Item(0).ToString.Length = 2 Then
CompleteIDPNumber = "00" & dt.Rows.Item(0).Item(0).ToString
ElseIf dt.Rows.Item(0).Item(0).ToString.Length = 3 Then
CompleteIDPNumber = "0" & dt.Rows.Item(0).Item(0).ToString
Else
CompleteIDPNumber = dt.Rows.Item(0).Item(0).ToString
End If
Else
CompleteIDPNumber = dt.Rows.Item(0).Item(0).ToString
End If
'Populate strings
Dim StoreName As String = CompleteIDPNumber & " - " & dt.Rows.Item(0).Item(1).ToString.Trim
Dim UISupports As Integer = 20 'This is the amount of characters that will fit in label space
If StoreName.Length > UISupports Then
StoreName = StoreName.Substring(0, UISupports).ToString.Trim & "..." & " (" & dt.Rows.Item(0).Item(3).ToString.Trim & ")"
Else
StoreName = StoreName & " (" & dt.Rows.Item(0).Item(3).ToString.Trim & ")"
End If
args.Brief_StoreName = StoreName
StoreName = Nothing 'We no longer need this, release it from memory
UISupports = Nothing 'We no longer need this, release it from memory
CompleteIDPNumber = Nothing 'We no longer need this, release it from memory
If dt.Rows.Item(0).Item(2) = 0 Or dt.Rows.Item(0).Item(2).ToString.Trim = "0" Then
args.Brief_POSNumber = "IS"
Else
args.Brief_POSNumber = dt.Rows.Item(0).Item(2).ToString.Trim
End If
args.Brief_Category = dt.Rows.Item(0).Item(3).ToString 'CAT
args.Brief_STCamera = dt.Rows.Item(0).Item(4).ToString 'Counter
args.Brief_Franch = dt.Rows.Item(0).Item(5).ToString
Ender:
e.Result = args
'Close connection
dt.Dispose()
command.Connection.Close()
command.Dispose()
End Using
End Sub
Private Sub BW_GetStoreBrief_RunWorkerCompleted(sender As Object, e As RunWorkerCompletedEventArgs) Handles BW_GetStoreBrief.RunWorkerCompleted
If e.Error IsNot Nothing Then
ListBox1.Items.Insert(0, Now.ToString("HH:mm:ss") & " | Error | Unable to connect to store DB.")
lblNotFound.Text = "Unable to connect to database."
Panel_NotFound.Visible = True
Panel_NotFound.BringToFront()
ErrorLogger.LogError(System.Reflection.MethodBase.GetCurrentMethod().Name, e.Error.Message, 0)
ElseIf e.Cancelled Then
Else
' Access variables through args
Dim args As BW_GetStoreBriefVariables = DirectCast(e.Result, BW_GetStoreBriefVariables) 'Convert the generic Object back into a MyParameters object
If args.Brief_StoreName = "" Then
ListBox1.Items.Insert(0, Now.ToString("hh:mm:ss") & " | Notice | IDP " & args.Current_IDP & " not found in database.")
'show warning panel
lblNotFound.Text = "Store not found in database."
Panel_NotFound.Visible = True
Panel_NotFound.BringToFront()
GoTo Ender
Else
'Store found update UI
lblBranchInfo_StoreName.Text = args.Brief_StoreName
lblBranchInfo_POSNumber.Text = args.Brief_POSNumber
lblBranchInfo_CameraType.Text = args.Brief_STCamera
Panel_NotFound.Visible = False
Panel_NotFound.SendToBack()
End If
args = Nothing
End If
Ender:
btnStoreDetails.Enabled = True
End Sub
As you can see i've tried to make sure I'm not leaving anything behind, but the memory keeps jumping up and doesn't go down. Overall we're talking about 35MBs being used when this codes been ran only a few times and nothing else is happening with the program/form. Because this is on a remote virtual machine the program can be open for days without being closed, and with the memory usage increasing each time it will become a very big issue. Any help would be appreciated.

vb.net how to detect if an email is undeliverable in a win form program

I have the following code:
Public Function VerstuurMail(ByVal strFrom As String, ByVal strTo As String, ByVal strSubject As String, ByVal strBody As String, ByVal strMailSMTP As String, ByVal MailUser As String, ByVal MailPassword As String, ByVal MailPort As Integer, Optional ByVal AttachmentFiles As String = "") As String
Try
'create the mail message
Dim mail As New MailMessage()
Dim basicCredential As New NetworkCredential(MailUser, MailPassword)
'set the addresses
mail.From = New MailAddress(strFrom)
mail.To.Add(strTo)
'set the content
mail.Subject = strSubject
If File.Exists(strBody) = True Then
Dim objReader As New System.IO.StreamReader(strBody, System.Text.Encoding.GetEncoding(1252))
mail.Body = objReader.ReadToEnd
objReader.Close()
End If
mail.IsBodyHtml = False
'send the message
Dim smtp As New SmtpClient(strMailSMTP)
smtp.DeliveryMethod = SmtpDeliveryMethod.Network
smtp.EnableSsl = True
'smtp.UseDefaultCredentials = True
smtp.Credentials = basicCredential
smtp.Port = MailPort
Dim AttachmentFile As String() = AttachmentFiles.Split("*")
For Each bestand In AttachmentFile
If System.IO.File.Exists(bestand) Then
mail.Attachments.Add(New Attachment(bestand))
Else
Call MessageBox.Show("File can't be found")
End If
Next
'Dim userState As Object = mail
'smtp.SendAsync(mail, userState)
'AddHandler smtp.SendCompleted, AddressOf SendCompletedCallback
smtp.Send(mail)
mailSent = True
smtp.Dispose()
Catch ex As Exception
mailSent = False
Call MessageBox.Show(ex.Message & vbCrLf & "Didn't sent to: " & strTo & vbCrLf & " with extra error message:" & vbCrLf & ex.ToString)
Finally
End Try
Return mailSent
End Function
This function is used in a program which reads a text file, with the parameters on one line, and is called as many lines there are. (in a loop)
This is working fine.
Now when the text file has a wrong email adress the function doesn't trow a error it just sent the email to nobody.
example: sent an mail to joe#gmail.com works, send an email to joe#hmail.com doesn't sent but doesn't give an error either.
I have googled but the examples said that I should use 'smtp.SendAsync(mail, userState)'
But then the program doesn't follow the loop anymore and no mails are being sent. I can't use the debugger and step through the code. It just jumps from one place to the other.
This is the other function:
Private Sub SendCompletedCallback(ByVal sender As Object, ByVal e As AsyncCompletedEventArgs)
Dim mail As MailMessage = CType(e.UserState, MailMessage)
'write out the subject
Dim subject As String = mail.Subject
'If e.Cancelled Then
' Call MessageBox.Show("Send canceled: " & Now & " token " & subject)
' MailLog &= "Send canceled" & vbCrLf
'End If
If Not e.Error Is Nothing Then
Call MessageBox.Show("Foutmelding op: " & Now & " onderwerp " & subject & " met error: " & e.Error.ToString())
MailLog = MailLog & "Niet verstuurd met als fout melding: " & e.Error.ToString() & vbCrLf
Else
'Call MessageBox.Show("Message sent at: " & Now)
MailLog = MailLog & "Bericht verstuurd op: " & Now & vbCrLf
End If
mailSent = True
End Sub
Thanks in advance. I hope somebody can put me in the right direction.
Brian
This is a data issue, not an issue with the actual mechanism to send email. The best you can do is to use a regular expression to make sure the email address is valid per the rules of RFC 2822, like this:
string email = txtemail.Text;
Regex regex = new Regex(#"^([\w\.\-]+)#([\w\-]+)((\.(\w){2,3})+)$");
Match match = regex.Match(email);
if (match.Success)
{
Response.Write(email + " is valid.");
}
else
{
Response.Write(email + " is invalid.");
}
Unfortunately for you, joe#hmail.com is a valid email address by the above logic, but is is not the intended joe#gmail.com address so it ends up in the wrong place. When it is discovered that it is the wrong email address, then changing the data to the right value is the only correct course of action.
Note: Generally, you verify someone's email address when they register for a website, thus the system "knows" the email address is legitimate/correct because they successfully received an email and entered a verification code or clicked on a link that verified with the website that they did get the email. This solves most data issues (misspellings, incorrectly entered values, etc.).
After two full days of testing and searching I have found that when using:
Dim userState As Object = mail
smtp.SendAsync(mail, userState)
AddHandler smtp.SendCompleted, AddressOf SendCompletedCallback
you should not close the smtpClient with smtp.Dispose()
The callback function kept getting the cancel error.
Also I used the backgroundworker for sending the many mails but the async is, in a way, a backgroundworker. So I had two backgroundworkers and that didn't work at all.
It took me two days.....
Brian

Is it safe to call SmtpClient.Dispose() in .NET?

I have a scenario where I need to send 100 emails in one shot (using a loop), but also I am not allowed to send 1 email per SMTP session.
Right now all 100 emails are sharing same SMTP session.
I was thinking that calling SmtpClient.Dispose() will take care of what I need. Please correct me if I am wrong.
So, basically 3 questions:
Will SmtpClient.Dispose() take care of what I need?
If Yes, is it safe to Dispose() SmtpClient without affecting other services on the
server?
If No, What would be the right approach to achieve what I
want?
Sample Code:
Private Shared Sub SendMail(ByVal MailServer As SmtpClient, ByVal body As String, ByVal Subject As String, ByVal FromEmail As String, _
ByVal ToEmailList As String, Optional ByVal AttFile As Attachment = Nothing)
Dim message As New MailMessage
Try
message.From = New MailAddress(FromEmail)
message.Subject = Subject
message.IsBodyHtml = False
message.Body = body
message.Priority = MailPriority.High
If Not AttFile Is Nothing Then
message.Attachments.Add(AttFile)
Else
message.Attachments.Add(AttFile)
End If
MailServer.Send(message)
Catch ex As Exception
Throw New ApplicationException("SERVICE1.SendMail ERROR -- Error sending email [ERROR]:[" & ex.Message.ToString & "] " & vbCrLf & "To:" & ToEmailList & vbCrLf & "From:" & FromEmail & vbCrLf & "Subject: " & Subject & vbCrLf & "Body: " & body)
End Try
message.Dispose()
End Sub
And this is how the method is being executed:
For Each Item In ItemListCollection
m_MailServer = New SmtpClient(MailServerName)
MailServer.Credentials = New System.Net.NetworkCredential(MailServerUserName, MailServerPassword)
SendMail(WeeklyMailServer, msgBody, msgSubject, MsgFromEmail, "xyz#abc.com", rptAttachment)
Next
You could wrap it in a using statement and ensure that it is disposed when execution leaves the block. And you can call Send multiple times in a loop using the same SmtpClient.
Using client = New SmtpClient()
For i As Integer = 0 To 99
Dim message = New MailMessage()
'initialization of whatever is needed
' message creation
client.Send(message)
Next
End Using
Inside execution loop, you can enclose the code in a Using block. This will use a separate smtpclient for each email and will dispose / close it properly.
For Each Item In ItemListCollection
using m_MailServer as New SmtpClient(MailServerName)
MailServer.Credentials = New System.Net.NetworkCredential(MailServerUserName, MailServerPassword)
SendMail(WeeklyMailServer, msgBody, msgSubject, MsgFromEmail, "xyz#abc.com", rptAttachment)
end using
Next

vb.net process start & stop

How can I create a process out of the below code so that I can tell when it starts & finishes?
Thanks in advance :)
Public Shared Function EmptyDirectory(ByVal mydir As String)
Try
'delete all directories
Dim myFolder As String
For Each myFolder In Directory.GetDirectories(mydir)
Directory.Delete(myFolder, True)
Next
'delete all files
Dim myFile As String
For Each myFile In Directory.GetFiles(mydir)
File.Delete(myFile)
Next
Catch Ex As Exception
'MsgBox(ex.Message)
End Try
Return False
End Function
FYI: This is what I thought would work:
Dim myProcess As System.Diagnostics.Process = New System.Diagnostics.Process()
myProcess.StartInfo.WindowStyle = System.Diagnostics.ProcessWindowStyle.Hidden
myProcess.Start(EmptyDirectory(DestDir))
Dim ClearDirectoryStartDateTime As String = DateTime.Now.ToString("d") & " " & DateTime.Now.ToString("HH:mm:ss")
StatusBoxName.Items.Add(ClearDirectoryStartDateTime & " - Cleaning")
' Wait until it ends.
myProcess.WaitForExit()
' Close the process to free resources.
myProcess.Close()
You might want to use threading. There's an interesting article on MSDN: http://msdn.microsoft.com/en-us/library/aa289178(v=vs.71).aspx