views:

228

answers:

10

Hi,

I'm developing an application where a user clicks/presses enter on a certain button in a window, the application does some checks and determines whether to send out a couple of emails or not, then show another window with a message.

My issue is, sending out the 2 emails slows the process noticeably, and for some (~8) seconds the first window looks frozen while it's doing the sending.

Is there any way I can have these emails sent on the background and display the next window right away?

Please don't limit your answer with "use X class" or "just use X method" as I am not all too familiarized with the language yet and some more information would be highly appreciated.

Thanks.

+5  A: 

It's not too complicated to simply send the message on a separate thread:

using System.Net.Mail;

Smtp.SendAsync(message);

Or, if you want to construct the whole message on the separate thread instead of rather just send it Asynchronously:

using System.Threading;
using System.Net.Mail;

var sendMailThread = new Thread(() => {
    var message=new MailMessage();
    message.From="from e-mail";
    message.To="to e-mail";
    message.Subject="Message Subject";
    message.Body="Message Body";

    SmtpMail.SmtpServer="SMTP Server Address";
    SmtpMail.Send(message);
});

sendMailThread.Start();
Justin Niessner
[ObsoleteAttribute("The recommended alternative is System.Net.Mail.SmtpClient. http://go.microsoft.com/fwlink/?linkid=14202")]
Andrey
also thread creation is recommended for long running processes, not for async tasks
Andrey
A: 

What you want to do is run the e-mail task on a separate thread so the main code can continue processing while the other thread does the e-mail work.

Here is a tutorial on how to do that: Threading Tutorial C#

JohnFx
+6  A: 

SmtpClient.SendAsync Method

Andrey
-1 OP specifically asked if you would not just specify what methods they needed to use, they wanted an example.
James
@James go to link, there is example there. " Examples [+] The following code example demonstrates calling this method."
Andrey
A: 

The easiest Solution is to create a BackgroundWorker and push the mails into a queue. Then just let the BackgroundWorker go through the queue and send each mail.

See also How to: Run an Operation in the Background

dbemerlin
A: 

Use the SmtpClient class and use the method SendAsync in the System.Net.Mail namespace.

CubanX
+3  A: 

Just because this is a little vague...I will be brief...

There are a lot of ways to do asynchronous or parallel work in c#/.net etc.

The fastest way to do what you want is to use a background worker thread which will avoid locking up your UI.

A tip with background worker threads : you cannot directly update the UI from them (thread affinity and Marshalling is just something you learn to deal with...)

Another thing to consider...if you use the standard System.Net.Mail type stuff to send the emails...be careful how you craft your logic. If you isolate it all in some method and call it over and over, it will likely have to tear down and rebuild the connection to the mail server each time and the latency involved in authentication etc will still slow the whole thing down unnecessarily. Send multiple e-mails through a single open connection to the mail server when possible.

fdfrye
+3  A: 

Try this:

var client = new System.Net.Mail.SmtpClient("smtp.server");
var message = new System.Net.Mail.MailMessage() { /* provide its properties */ };
client.SendAsync(message, null);
kbrimington
I tried this solution, however SendAsync takes at least 2 parameters. I used a correct overload but the emails are not being sent, I've tried debugging and in the SendAsync line the recipient address, subject, body, etc. are correct. I've no idea why it's not sending them when Send does.
Eton B.
$eton-b: Sorry about the syntax error. I fixed it.
kbrimington
A: 

Using the Task Parallel Library in .NET 4.0, you can do:

Parllel.Invoke(() => { YourSendMailMethod(); });

Also, see cristina manu's blog post about Parallel.Invoke() vs. explicit task management.

JaredReisinger
That's not going to work. The `Invoke` function doesn't return until all actions inside are complete.
Gabe
Crud... you're right, of course. I was thinking of `Task.Factory.StartNew()`.
JaredReisinger
+5  A: 

As it's a small unit of work you should use ThreadPool.QueueUserWorkItem for the threading aspect of it. If you use the SmtpClient class to send your mail you could handle the SendCompleted event to give feedback to the user.

ThreadPool.QueueUserWorkItem(t =>
{
    SmtpClient client = new SmtpClient("MyMailServer");
    MailAddress from = new MailAddress("[email protected]", "My Name", System.Text.Encoding.UTF8);
    MailAddress to = new MailAddress("[email protected]");
    MailMessage message = new MailMessage(from, to);
    message.Body = "The message I want to send.";
    message.BodyEncoding =  System.Text.Encoding.UTF8;
    message.Subject = "The subject of the email";
    message.SubjectEncoding = System.Text.Encoding.UTF8;
    // Set the method that is called back when the send operation ends.
    client.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);
    // The userState can be any object that allows your callback 
    // method to identify this send operation.
    // For this example, I am passing the message itself
    client.SendAsync(message, message);
});

private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
{
        // Get the message we sent
        MailMessage msg = (MailMessage)e.UserState;

        if (e.Cancelled)
        {
            // prompt user with "send cancelled" message 
        }
        if (e.Error != null)
        {
            // prompt user with error message 
        }
        else
        {
            // prompt user with message sent!
            // as we have the message object we can also display who the message
            // was sent to etc 
        }

        // finally dispose of the message
        if (msg != null)
            msg.Dispose();
}

By creating a fresh SMTP client each time this will allow you to send out emails simultaneously.

James
So I'm trying to work on this answer, as it seems the most viable (simplicity/similar to what I'm using) for me. What exactly is used as "userState", though? Do I HAVE to use a thread? I simply changed my Send method for this: string someString = "Message";smtp.SendAsync(message,someString);to no avail. I'll implement all your solution and see what I'm doing wrong.
Eton B.
@Eton if you look at the example I have indicated what the userState variable does. It is an object that is passed to the Callback method when the event is raised. In the example I am using it as a unique identifier but basically you can pass whatever you want into it. No it is not a necessity, if you don't need to use it in the callback method then simply pass in null.
James
@Eton - No you don't *have* to use a thread at all. If you take the email sending code out of the QueueUserWorkItem call and put it directly behind the click event of the button it should work all the same as you are creating a separate SmtpClient each time and the email is being sent using SendAsync (which won't block the UI).
James
This will not work since the message is disposed before it is sent. Either use Send or dispose the message in the callback. (note that the msdn example only disposes the message if send is cancelled. Can't find anything in the documentation supporting it)
adrianm
@James - thanks for your response(s). I want to test the solution in the simplest way (no thread) first then I can start making it more robust. The UI no longer blocks but for some reason the emails aren't being sent. I checked the call to the SendAsync method and the parameters(recipient,body,etc) are all correct at that point. The only thing I'm not doing is creating a SmtpClient every time (its created using a constructor once, receiving parameters from a config file). Could this be affecting?
Eton B.
@adrianm - right on point, I thought about that too. I commented out the dispose line and it worked. Pretty obvious now that I think about it..
Eton B.
@adrianm: Good spot, the example for sending the email was taken straight from the MSDN example. I forgot to move the dispose of the message to the callback, will update.
James
@Eton - adrian already stated why it wasn't sending I have updated my answer to suit....however, heed my warning in my previous comment about only using 1 SmtpClient. SendAsync does **not** allow multiple emails to be sent out simultaneously, if there will be a case that 2 emails could be sent out at the same time at any point you would be safer to just recreate the SmtpClient everytime... the performance cost in doing this would be negligiable.
James
@James - Thanks a lot for all your feedback. I picked it as my answer for obvious reasons. If you could compliment it with a way to dispose the message once the email is sent that'd be awesome.
Eton B.
@Eton - already beat you to it! See updated answer.
James
@James - Yep, that pretty much wraps this up. Thanks again, used my first vote up :)
Eton B.
I have 2 questions regarding this example. Why do you need to dispose the message, does it hold external resources? and 2 if you are already using another thread why also use SendAsync? what is the benefit at this point?
Sruly
@Sruly - It is best practise to always dispose objects that implement IDisposable (usually you would want to wrap it in a using statement, but in this case we can't). Also if your mail message had attachments then you would not be able to touch them, even on disk, until the message was destroyed as it locks them down. As for using SendAsync inside the thread. The benefit of using this is you get the callback and from there you can determine the result of the send. If we wanted to use the Send method instead we would need to wrap this in a try...catch block incase it failed.
James
@james thanks for the clarifications :)
Sruly
A: 

You can write -the intent to send that message- to a transactional queue (such as a database table). You can than have another program (such as a Windows Service) that processes the queue.

When you do this you can not only send messages in the background without disturbing your main application, but you can also ensure that the message is send as part of a bigger business transaction. This is useful in situations were you want to prevent the message to be sent when the (database) transaction is rolled back.

When doing this type of asynchronous command processing, it also allows to implement retry mechanisms and monitoring when execution of asynchronous operations fail. For instance, when a message was failed be send, you could fix that command reschedule it.

Steven