views:

654

answers:

3

Hi,

I need to send emails asychronously through a console application. I need to do some DB updates on the callback but my application is exiting before the callback code gets run!

How can I stop this from happening in a nice manner rather than simply guessing how long to wait before exiting. I would imagine the Async calls get placed in some form of thread? Is it possible to check if any are waiting to be called?

Sample Code

private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e)
{
   // Get the unique identifier for this asynchronous operation.
   String token = (string) e.UserState;
   if (e.Cancelled)
   {
      Console.WriteLine("[{0}] Send canceled.", token);
   }
   if (e.Error != null)
   {
      Console.WriteLine("[{0}] {1}", token, e.Error.ToString());
   } 
   else
   {
       // update DB
       Console.WriteLine("Message sent.");
   }
}

public static void Main(string[] args)
{
    var users = Repository.GetUsers();
    SmtpClient client = new SmtpClient("Host");
    client.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback);
    MailAddress from = new MailAddress("[email protected]", "System", Encoding.UTF8);
    foreach (var user in users)
    {
        MailAddress to = new MailAddress(user.Email);
        MailMessage message = new MailMessage(from, to);
        message.Body = "This is a test";
        message.BodyEncoding =  System.Text.Encoding.UTF8;
        message.Subject = "test message 1" + someArrows;
        message.SubjectEncoding = System.Text.Encoding.UTF8;
        string userState = String.Format("Message for user id {0}", user.ID);
        client.SendAsync(message, userState);
        message.Dispose();   
    }

    // need to wait here until I have received a callback for each message
    // otherwise the application will exit
}
+2  A: 

Create a ManualResetEvent call WaitOne one it on it before exiting. When the last email/dbupdate is performed, call Set on the ManualResetEvent.


public static void Main(string[] args)
{
    object someArrows = ">>>";
    var users = Repository.GetUsers();
    SmtpClient client = new SmtpClient("Host");
    client.SendCompleted += SendCompletedCallback;
    MailAddress from = new MailAddress("[email protected]", "System", Encoding.UTF8);
    int numRemaining = users.Length;
    using(ManualResetEvent waitHandle = new ManualResetEvent(numRemaining == 0))
    {
        object numRemainingLock = new object();
        foreach(var user in users)
        {
            MailAddress to = new MailAddress(user.Email);
            MailMessage message = new MailMessage(from, to);
            try
            {
                message.Body = "This is a test";
                message.BodyEncoding = System.Text.Encoding.UTF8;
                message.Subject = "test message 1" + someArrows;
                message.SubjectEncoding = System.Text.Encoding.UTF8;
                string userState = String.Format("Message for user id {0}", user.ID);
                client.SendCompleted += delegate
                {
                    lock(numRemainingLock)
                    {
                        if(--numRemaining == 0)
                        {
                            waitHandle.Set();
                        }
                    }
                };
                client.SendAsync(message, userState);
            }
            catch
            {
                message.Dispose();
                throw;
            }
        }
        waitHandle.WaitOne();
    }
}
hjb417
Hi could you provide a sample of how you would advise this be done? I have updated my answer to show you an example
James
You are setting SendCompleted += SendCompletedCallback, but then in the loop you are assigning SendCompleted to a delegate? Does this mean the delegate code gets run and then the SendCompletedCallback gets called?
James
Yep. But note, the ordering in which the callbacks are called is not guaranteed. So, the SendCompleted may either call SendCompletedCallback 1st or my delegate 1st.
hjb417
A: 

I have this message: An asynchronous call is already in progress. It must be completed or canceled before you can call this method.

luutai
@luutai, you are trying to make 2 asynchronous calls at the same time. The SmtpClient won't allow this you need to wait for the first call to finish. An alternative is to use SendAsync in different threads.
James
A: 

I found that i had to create a neu instant SmtpClient in loop: Foreach, now it works perfectly

luutai
@luutai you should just add comments to your original answer. Yes creating new instances of SmtpClient is another way. However, its whether you can take the cost of creating a new instance of SmtpClient each time. In my case I couldn't.
James