Why can SmtpClient.SendAsync only be called once? - c#

I'm trying to write a notification service (for completely legit non-spam purposes) in .NET using SmtpClient. Initially I just looped through each message and sent it, however this is slow and I would like to improve the speed. So, I switched to using 'SendAsync', but now get the following error on the second call:
An asynchronous call is already in progress.
I read this to mean that MS crippled System.Net.Mail to prevent mass-mailers. Is this correct? If so, is there a better way to do this in .NET, and still be able to log the results of each email(which is important to our client). If not, why can SendAsync only be called once?

According to the documentation:
After calling SendAsync, you must wait
for the e-mail transmission to
complete before attempting to send
another e-mail message using Send or
SendAsync.
So to send multiple mails at the same time you need multiple SmtpClient instances.

You might be able to use the following:
ThreadPool.QueueUserWorkItem(state => client.Send(msg));
This should allow your messages to be queued and sent as threads become available.

Obviously, this is not an attempt to stop mass mailers.
The reason is that the SmtpClient class is not thread safe. If you want to send multiple emails simultaneously, you have to spawn a few worker threads (there are a few ways to do that in the .NET Framework) and create separate instances of SmtpClient in each of them.

I think you misunderstand the XXXAsync class of methods. The purpose of these asynchronous calls is to allow the program to continue running, without the need of the method to finish processing and return first. You can then proceed with the result later by subscribe to something like XXXReceived event of the object.
To send more than one mail simultaneously, you may consider using more Threads.

You may only send one at a time per SMTP client. If you wish to make more than one send call, create more than one SMTP client.
HTH,
Colby Africa

As noticed by everyone else here, you can only send one email at a time, but the way to send another once the first has been sent is to handle the .SendCompleted event of the SmtpClient class, and then move on to the next email and send that.
If you want to send many emails simultaneously, then as the others have said, use multiple SmtpClient objects.

There is a reason to reuse the SmtpClient, it limits the # of connections to the SMTP server. I cannot instantiate a new class SmtpClient class for each thread the reports are building on or the SMTP server will balk with too many connections error. This is the solution I came up with when I couldn't find an answer here.
I ended up using an AutoResetEvent for keeping everything in sync. That way, I can keep calling my SendAsync in each thread, but wait for it to process the email and use the SendComplete event to reset it so the next one can continue.
I setup the Auto Reset Event.
AutoResetEvent _autoResetEvent = new AutoResetEvent(true);
I setup the shared SMTP Client when my class is instantiated.
_smtpServer = new SmtpClient(_mailServer);
_smtpServer.Port = Convert.ToInt32(_mailPort);
_smtpServer.UseDefaultCredentials = false;
_smtpServer.Credentials = new System.Net.NetworkCredential(_mailUser, _mailPassword);
_smtpServer.EnableSsl = true;
_smtpServer.SendCompleted += SmtpServer_SendCompleted;
Then when I call the send async, I wait for the event to clear, then send the next one.
_autoResetEvent.WaitOne();
_smtpServer.SendAsync(mail, mail);
mailWaiting++;
I use the SMTPClient SendComplete event to reset the AutoResetEvent so the next email will send.
private static void SmtpServer_SendCompleted(object sender, System.ComponentModel.AsyncCompletedEventArgs e)
{
MailMessage thisMesage = (MailMessage) e.UserState;
if (e.Error != null)
{
if (e.Error.InnerException != null)
{
writeMessage("ERROR: Sending Mail: " + thisMesage.Subject + " Msg: "
+ e.Error.Message + e.Error.InnerException.Message);
}
else
{
writeMessage("ERROR: Sending Mail: " + thisMesage.Subject + " Msg: " + e.Error.Message);
}
}
else
{
writeMessage("Success:" + thisMesage.Subject + " sent.");
}
if (_messagesPerConnection > 20)
{ /*Limit # of messages per connection,
After send then reset the SmtpClient before next thread release*/
_smtpServer = new SmtpClient(_mailServer);
_smtpServer.SendCompleted += SmtpServer_SendCompleted;
_smtpServer.Port = Convert.ToInt32(_mailPort);
_smtpServer.UseDefaultCredentials = false;
_smtpServer.Credentials = new NetworkCredential(_mailUser, _mailPassword);
_smtpServer.EnableSsl = true;
_messagesPerConnection = 0;
}
_autoResetEvent.Set();//Here is the event reset
mailWaiting--;
}

Related

How to defer a Azure Service Bus message?

Current I'm using Microsoft.Azure.ServiceBus.IQueueClient to RegisterMessageHandler, and then the message I receive is of type Microsoft.Azure.ServiceBus.Message.
According to the documentation:
Message deferral APIs The API is BrokeredMessage.Defer or
BrokeredMessage.DeferAsync in the .NET Framework client,
MessageReceiver.DeferAsync in the .NET Standard client, and
IMessageReceiver.defer or IMessageReceiver.deferAsync in the Java
client.
...but none of those libraries seam to relate to the classes I'm actually using. How do I defer? What classes and stuff do I have to use in order to be able to defer messages? All the samples above dont give enough code snippets to explain it.
Update as requested by #Gaurav
from your answer, I can see my message has that property:
message.ScheduledEnqueueTimeUtc = DateTime.UtcNow.AddHours(1);
but the queueClient also has this method:
queueClient.ScheduleMessageAsync(message, DateTime.UtcNow.AddHours(1));
I'm going to try 'scheduledMessageAsync' as I cant see how to communicate that I've set ScheduledEnqueueTimeUtc without calling the queueClient
Microsoft.Azure.ServiceBus.Message has a property called ScheduledEnqueueTimeUtc. Just set the value of this property to a date/time value in future when you want the message to appear in the queue. Message will be hidden till that time and will only appear in the queue at that date/time.
UPDATE
So I ran a test and confirmed that both ScheduledEnqueueTimeUtc and ScheduleMessageAsync works. I used version 4.1.1 for Microsoft.Azure.ServiceBus SDK.
Here's the code I wrote:
static void Main(string[] args)
{
var connectionString = "my-connection-string";
var queueName = "test";
QueueClient queueClient = new QueueClient(connectionString, queueName);
Message msg1 = new Message()
{
Body = Encoding.UTF8.GetBytes("This message has ScheduledEnqueueTimeUtc property set. It will appear in queue after 2 minutes. Current date/time is: " + DateTime.Now),
ScheduledEnqueueTimeUtc = DateTime.UtcNow.AddMinutes(2)
};
queueClient.SendAsync(msg1).GetAwaiter().GetResult();
Message msg2 = new Message()
{
Body = Encoding.UTF8.GetBytes("This message is sent via ScheduleMessageAsync method. It will appear in queue after 2 minutes. Current date/time is: " + DateTime.Now)
};
queueClient.ScheduleMessageAsync(msg2, new DateTimeOffset(DateTime.UtcNow.AddMinutes(2))).GetAwaiter().GetResult();
Console.ReadLine();
}
And this is what I see when I fetch the messages in Peek-Lock mode:
Using the message deferral APIs like BrokeredMessage.Defer or BrokeredMessage.DeferAsync will defer the message.
Defering a message will change the state of the message from Active to Deferred. The message can be later retrieved based on the sequence number.
ScheduleMessageAsync() is used to schedule the delivery of message (sends a message at specified time). It cannot be used after receiving a message.
I've coded the solution I was looking for, here is the basic outline:
inside an asynchronous method (runs its own thread)
public async Task InitialiseAndRunMessageReceiver()
start an infinite loop that reads the message
receiver = new MessageReceiver(serviceBusConnectionString, serviceBusQueueName, ReceiveMode.PeekLock);
while (true) { var message = await receiver.ReceiveAsync(); ... more code... }
once you know you are about to start your long task, defer the message, but store the message.SystemProperties.SequenceNumber. this keeps it in the queue but prevents it from being re-delivered.
await receiver.DeferAsync(message.SystemProperties.LockToken);
and when you finally done ask for the message again using the message.SystemProperties.SequenceNumber, and complete the message as if it weren't deferred
var message = receiver.ReceiveDeferredMessageAsync(message.SystemProperties.SequenceNumber);
receiver.CompleteAsync(message.Result.SystemProperties.LockToken);
and your message will be removed from the queue.
much of my confusion was caused by the libraries being named similarly with overlapping lifespans.
Microsoft.Azure.ServiceBus.Core.MessageReceiver is the message receiver above
Old question, but what suited my situation was deleting the message and posting a copy using ScheduleMessageAsync (there is a copy method somewhere). Then the message would just come back at the desired time.

Should SmtpClient be created (and disposed) per message?

What is the proper way of using SmtpClient in terms of creating/disposing the object. I have one SmtpClient per message, but not sure if this is correct if I want to send multiple emails. Perhaps it's more efficient/proper to create the client, send multiple message and then dispose?
public void SendMail(MailMessage mail)
{
using (var smtpClient = new SmtpClient())
{
smtpClient.Send(mail);
}
}
Looking at the source code of the SmtpClient, it seems it is built to have multiple connections open. It doesn't seem a problem to me to use that to send multiple messages in a tight loop (don't let the object alive for hours, dispose it as soon you are ready sending).
Creating and disposing the object may have a negative impact on your performance, so that would be a good reason not to create a lot of objects.

Multithreading using AsyncCallback and GUI controls

Multithread programming is a new concept for me. I’ve done a bunch of reading and even with many examples, I just can’t seem to figure it out. I'm new to C# and programming.
I have a winform project with lots of custom controls I’ve imported and will utilize many tcpclients. I’m trying to get each control to be hosted on it’s own separate thread. Right now, I’m trying to get 1 control to behave appropriately with it’s own thread.
I'll show you what I have and then follow up with some questions regarding guidance.
string asyncServerHolder; // gets the server name from a text_changed event
int asyncPortHolder; // gets the port # from a text_changed event
TcpClient wifiClient = new TcpClient();
private void btnStart_Click(object sender, EventArgs e)
{
... // variable initialization, etc.
... // XML setup, http POST setup.
send(postString + XMLString); // Content to send.
}
private void send(string msg)
{
AsyncCallback callBack = new AsyncCallback(ContentDownload);
wifiClient.BeginConnect(asyncServerHolder, asyncPortHolder, callBack, wifiClient);
wifiClient.Client.Send(System.Text.Encoding.ASCII.GetBytes(msg));
}
private void ContentDownload(IAsyncResult result)
{
if (wifiClient.Connected)
{
string response4 = "Connected!!"; //debug msg
byte[] buff = new byte[1024];
int i = wifiClient.Client.Receive(buff);
do
{
response1 = System.Text.Encoding.UTF8.GetString(buff, 0, i);
} while (response1.Length == 0);
response2 = response1.Substring(9, 3); // pick out status code to be displayed after
wifiClient.Client.Dispose();
wifiClient.Close();
}
}
If you're knowledgeable about this, I bet you see lots of problems above. As it stands right now, I always get an exception one my first iteration of running this sequence:
"A request to send or receive data was disallowed because the socket is not connected and (when sending on a datagram socket using a sendto call) no address was supplied"
Why is this? I have confirmed that my asyncServerHolder and my asyncPortHolder are correct. My second iteration of attempting allowed me to see response4 = "Connected!!" but I get a null response on response1.
Eventually I'd like to substitute in my user controls which I have in a List. I'd just like to gracefully connect, send my msg, receive my response and then allow my form to notify me from that particular control which plays host to that tcp client. My next step would be link up many controls.
Some questions:
1) Do I need more TCP clients? Should they be in a list and be the # of controls I have enabled at that time of btnStart_Click?
2) My controls are on my GUI, does that mean I need to invoke if I'm interacting with them?
3) I see many examples using static methods with this context. Why is this?
Thanks in advance. All criticism is welcome, feel free to be harsh!
BeginConnect returns immediately. Probably, no connection has been established yet when Send runs. Make sure that you use the connection only after having connected.
if (wifiClient.Connected) and what if !Connected? You just do nothing. That's not a valid error recovery strategy. Remove this if entirely.
In your read loop you destroy the previously read contents on each iteration. In fact, you can't split up an UTF8 encoded string at all and decode the parts separately. Read all bytes into some buffer and only when you have received everything, decode the bytes to a string.
wifiClient.Client.Dispose();
wifiClient.Close();
Superstitious dispose pattern. wifiClient.Dispose(); is the canonical way to release everything.
I didn't quite understand what "controls" you are talking about. A socket is not a control. UI controls are single-threaded. Only access them on the UI thread.
Do I need more TCP clients?
You need one for each connection.
Probably, you should use await for all blocking operations. There are wrapper libraries that make the socket APIs usable with await.

How do you loop in C# until a new email has arrived?

using System;
using Limilabs.Mail;
using Limilabs.Client.POP3;
class Program
{
static void Main(string[] args)
{
using (System.IO.StreamWriter file = new System.IO.StreamWriter(#"C:\Users\*******\Desktop\WriteLines.txt", true)){
using (Pop3 pop3 = new Pop3())
{
Console.WriteLine("Connecting...");
pop3.ConnectSSL("pop3.live.com"); // or ConnectSSL for SSL
pop3.Login("****#live.com", "****");
// Receive all messages and display the subject
MailBuilder builder = new MailBuilder();
foreach (string uid in pop3.GetAll())
{
IMail email = builder.CreateFromEml(
pop3.GetMessageByUID(uid));
file.WriteLine("Header: " + email.Subject);
file.WriteLine("Message: " + email.Text);
}
pop3.Close();
}
}
}
}
So I have this problem where I want to have this program running 24/7; it is still incomplete but this is just to show basic C# retrieving emails and such. How can I have a loop in C# that only gets halted or forced to anything only when a new email arrives in the inbox? I would like the program only to actually do something upon getting an email sent to in in realtime. I know I can go and print out my emails one by one all at once but I want it to be running and only when a new message is received do I want i to do anything. For example idk, if I was to send a message like clean desktop, it would stop and be parsed and if a valid command sequence specified by another program I am going to make, then it will carry out that command and then keep on looping endlessly waiting for another new message. It'll basically be a server running 24/7 that only responds to new emails. That is what I am going for. All help is greatly appreciated.
Pop3 is a polling protocol. It does not allow to inform you that a new mail has arrived. You would have to e.g. the Exchange protocol that supports this afaik. Thus, for POP3 mailboxes, mail programs usually poll for new emails every e.g. 15min. I'd suspect that your POP3-library does not support this directly. Thus, you would have to use a Timer class and poll for new emails.
Documentation for the Timer class can be found in the MSDN library:
http://msdn.microsoft.com/en-us/library/system.timers.timer%28v=vs.110%29.aspx
You would probably want to use a windows service that polls your email inbox.
A similar question was asked about how to do this here. One of the answers provides a good layout in how to do this.
In addition, if you are trying have your website perform this functionality (because you don't have access to install a windows service) you could use Quartz.net highlighted in this question.

Proper way to asynchronously send an email in ASP.NET... (am i doing it right?)

When a user registers on my website, I don't see why I need to make him "wait" for the smtp to go through so that he gets an activation email.
I decided I want to launch this code asynchronously, and it's been an adventure.
Lets imagine I have a method, such as:
private void SendTheMail() { // Stuff }
My first though.. was threading. I did this:
Emailer mailer = new Emailer();
Thread emailThread = new Thread(() => mailer.SendTheMail());
emailThread.Start();
This works... until I decided to test it for error-handling capability. I purposely broke the SMTP server address in my web.config and tried it. The scary result was that IIS basically BARFED with an unhandled exception error on w3wp.exe (it was a windows error! how extreme...) ELMAH (my error logger) did NOT catch it AND IIS was restarted so anyone on the website had their session erased. Completely unacceptable result!
My next thought, was to do some research on Asynchronous delegates. This seems to work better because exceptions are being handled within the asynch delegate (unlike the thread example above). However, i'm concerned if i'm doing it wrong or maybe I'm causing memory leaks.
Here's what i'm doing:
Emailer mailer = new Emailer();
AsyncMethodCaller caller = new AsyncMethodCaller(mailer.SendMailInSeperateThread);
caller.BeginInvoke(message, email.EmailId, null, null);
// Never EndInvoke...
Am I doing this right?
There was a lot of good advice that I upvoted here... such as making sure to remember to use IDisposable (i totally didn't know). I also realized how important it is to manually catch errors when in another thread since there is no context -- I have been working on a theory that I should just let ELMAH handle everything. Also, further exploration made me realize I was forgetting to use IDisposable on mailmessage, too.
In response to Richard, although I see that the threading solution can work (as suggested in my first example) as long as i'm catching the errors... there's still something scary about the fact that IIS completely explodes if that error isn't caught. That tells me that ASP.NET/IIS never meant for you to do that... which is why i'm leaning towards continuing to use .BeginInvoke/delegates instead since that doesn't mess up IIS when something goes wrong and seems to be more popular in ASP.NET.
In response to ASawyer, I was totally surprised that there was a .SendAsync built into the SMTP client. I played with that solution for a while, but it doesn't seem to do the trick for me. Although I can skip through the client of code that does SendAsync, the page still "waits" until the SendCompleted event is done. My goal was to have the user and the page move forward while the email is getting sent in the background. I have a feeling that I might still be doing something wrong... so if someone comes by this they might want to try it themselves.
Here's my full solution for how I sent emails 100% asynchronously in addition with ELMAH.MVC error logging. I decided to go with an expanded version of example 2:
public void SendThat(MailMessage message)
{
AsyncMethodCaller caller = new AsyncMethodCaller(SendMailInSeperateThread);
AsyncCallback callbackHandler = new AsyncCallback(AsyncCallback);
caller.BeginInvoke(message, callbackHandler, null);
}
private delegate void AsyncMethodCaller(MailMessage message);
private void SendMailInSeperateThread(MailMessage message)
{
try
{
SmtpClient client = new SmtpClient();
client.Timeout = 20000; // 20 second timeout... why more?
client.Send(message);
client.Dispose();
message.Dispose();
// If you have a flag checking to see if an email was sent, set it here
// Pass more parameters in the delegate if you need to...
}
catch (Exception e)
{
// This is very necessary to catch errors since we are in
// a different context & thread
Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
}
}
private void AsyncCallback(IAsyncResult ar)
{
try
{
AsyncResult result = (AsyncResult)ar;
AsyncMethodCaller caller = (AsyncMethodCaller)result.AsyncDelegate;
caller.EndInvoke(ar);
}
catch (Exception e)
{
Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
Elmah.ErrorLog.GetDefault(null).Log(new Error(new Exception("Emailer - This hacky asynccallback thing is puking, serves you right.")));
}
}
As of .NET 4.5 SmtpClient implements async awaitable method
SendMailAsync.
As a result, to send email asynchronously is as the following:
public async Task SendEmail(string toEmailAddress, string emailSubject, string emailMessage)
{
var message = new MailMessage();
message.To.Add(toEmailAddress);
message.Subject = emailSubject;
message.Body = emailMessage;
using (var smtpClient = new SmtpClient())
{
await smtpClient.SendMailAsync(message);
}
}
If you are using .Net's SmtpClient and MailMessage classes, you should take note of a couple things. First, expect errors on the send, so trap and handle them. Second, in .Net 4 there were some changes to these classes, and both now implement IDisposable (MailMessage since 3.5, SmtpClient new in 4.0). Because of this, your creation of the SmtpClient and the MailMessage should be wrapped in using blocks or explicitly disposed. This is a breaking change some people are unaware of.
See this SO question for more info on disposing when using async sends:
What are best practices for using SmtpClient, SendAsync and Dispose under .NET 4.0
Are you using the .Net SmtpClient to send email? It can send asynch messages already.
Edit - If Emailer mailer = new Emailer(); is not a wrapper over SmtpClient, this won't be so useful I imagine.
Threading isn't the wrong option here, but if you don't handle an exception yourself, it will bubble up and crash your process. It doesn't matter which thread you do that on.
So instead of mailer.SendTheMail() try this:
new Thread(() => {
try
{
mailer.SendTheMail();
}
catch(Exception ex)
{
// Do something with the exception
}
});
Better yet, use the asynchronous capabilities of the SmtpClient if you can. You'll still need to handle exceptions though.
I would even suggest you have a look at .Net 4's new Parallet Task library. That has extra functionality which lets you handle exceptional cases and works well with ASP.Net's thread pool.
So, why not have a separate poller/service which deals exclusively with sending emails? Thus, allowing your registration post-back to execute in only the time it takes to write to the database/message queue and delaying the sending of the email til the next polling interval.
I'm pondering the same issue just now and I'm thinking that I really don't want to even initiate the email sending within the server post back request. The process behind serving the web pages should be interested in getting a response back to the user ASAP, the more work you try to do the slower it will be.
Have a look at the Command Query Segregation Principal (http://martinfowler.com/bliki/CQRS.html). Martin Fowler explains that different models can be used in the command part of an operation than are used in the query part. In this scenario the command would be "register user", the query would be the activation email, using the loose analogy. The pertinent quote would probably be:
By separate models we most commonly mean different object models, probably running in different logical processes
Also worth a read is the Wikipedia article on CQRS (http://en.wikipedia.org/wiki/Command%E2%80%93query_separation). An important point which this highlights is:
it is clearly intended as a programming guideline rather than a rule for good coding
Meaning, use it where your code, program execution and programmer understanding would benefit. This being a good example scenario.
This approach has the added benefit of negating all the mufti-threading concerns and the headaches all that can bring.
I worked same issue for my project:
First tried Thread as you do:
- I loose context
- Exception handling problem
- Commonly said, Thread are bad idea on IIS ThreadPool
So I switch and try with asynchronously:
- 'asynchronously' is fake in a asp.net web application. It just put queue calls and swicth the context
So I make windows service and retrive the values through sql table: happy end
So for quick solution: from ajax side make async call tell the user fake yes, but continue your sending job in your mvc controller
Use this way-
private void email(object parameters)
{
Array arrayParameters = new object[2];
arrayParameters = (Array)parameters;
string Email = (string)arrayParameters.GetValue(0);
string subjectEmail = (string)arrayParameters.GetValue(1);
if (Email != "Email#email.com")
{
OnlineSearch OnlineResult = new OnlineSearch();
try
{
StringBuilder str = new StringBuilder();
MailMessage mailMessage = new MailMessage();
//here we set the address
mailMessage.From = fromAddress;
mailMessage.To.Add(Email);//here you can add multiple emailid
mailMessage.Subject = "";
//here we set add bcc address
//mailMessage.Bcc.Add(new MailAddress("bcc#site.com"));
str.Append("<html>");
str.Append("<body>");
str.Append("<table width=720 border=0 align=left cellpadding=0 cellspacing=5>");
str.Append("</table>");
str.Append("</body>");
str.Append("</html>");
//To determine email body is html or not
mailMessage.IsBodyHtml = true;
mailMessage.Body = str.ToString();
//file attachment for this e-mail message.
Attachment attach = new Attachment();
mailMessage.Attachments.Add(attach);
mailClient.Send(mailMessage);
}
}
protected void btnEmail_Click(object sender, ImageClickEventArgs e)
{
try
{
string To = txtEmailTo.Text.Trim();
string[] parameters = new string[2];
parameters[0] = To;
parameters[1] = PropCase(ViewState["StockStatusSub"].ToString());
Thread SendingThreads = new Thread(email);
SendingThreads.Start(parameters);
lblEmail.Visible = true;
lblEmail.Text = "Email Send Successfully ";
}
If you want to detect leaks, then you need to use a profiler like this one:
http://memprofiler.com/
I don't see anything wrong with your solution, but can almost guarantee you that this question will be closed as subjective.
One other option is to use jQuery to make an ajax call to the server and spark the e-mail flow. That way, the UI is not locked up.
Good luck!
Matt

Categories