2

I am using asp.net v4 c# and I have a list of email addresses. I want one of my admins to be able to type a message, press "SEND" and for the emails to go out one by one.

The best way I think is to make use of the async methods in .net? OnClick of the send button, I take the list of email addresses then call the async method. I just don't know how to get it to loop through the list one by one and fire off the email.

Is needs to be done one at a time so that I can save a copy of the email sent against that user's account.

Here's what I have so far (cobbled together from tutorials / posts on here)

protected void btnSend_Click(object sender, EventArgs e)
{
    //get a list of email addresses and send to them
    //these will come from the database once testing comp
    List<string> emails = new List<string>();
    emails.Add("test1@test.com");
    emails.Add("test2@test.com");
    emails.Add("test3@test.com");
    emails.Add("test4@test.com");
    emails.Add("test5@test.com");
    emails.Add("test6@test.com");
    emails.Add("test7@test.com");

    SendingDelegate worker = new SendingDelegate(DoSend);
    AsyncCallback completedCallback = new AsyncCallback(DoSendCompletedCallBack);

    lblThreadDetails.Text = "sending to " + emails.Count.ToString() + " email addresses";

    worker.BeginInvoke(completedCallback, AsyncOperationManager.CreateOperation(null));
    sending = true;

}

//boolean flag which indicates whether the async task is running
private bool sending = false;

private delegate bool SendingDelegate();

private bool DoSend()
{
    //send messages
    //emails sent here and saved in the DB each time

    //give the user some feed back on screen. X should be the email address.
    lblThreadDetails.Text = "processing " + x.ToString();
    Thread.Sleep(1000);
    return false;
}

private void DoSendCompletedCallBack(IAsyncResult ar)
{
    //get the original worker delegate and the AsyncOperation instance
    SendingDelegate worker = (SendingDelegate)((AsyncResult)ar).AsyncDelegate;

    //finish the asynchronous operation
    bool success = worker.EndInvoke(ar);
    sending = false;

    if (success)
    {
        //perform sql tasks now that crawl has completed
        lblThreadDetails.Text = "all done!";
    }
}

I basically need to put the calling of the async function in to a loop where I go through the list of email addresses.

Does this approach make sense?

John Saunders
  • 160,644
  • 26
  • 247
  • 397
Matt Facer
  • 3,103
  • 11
  • 49
  • 91
  • Tangent: you do know you don't really need to `new` up delegate objects anymore? At least to me that would make the code more readable since you don't need to trace back to where the delegate variable is pointing. – millimoose Jan 21 '14 at 11:42
  • I.e. you can use `(DoSend as Action).BeginInvoke(DoSendCompletedCallback, AsyncOperationManager.CreateOperation(null))` – millimoose Jan 21 '14 at 11:47
  • 1
    Also, there's one glaring issue in not your approach but your test harness, which is updating the GUI from background operations. Most GUI systems are single-threaded, that is creating and updating the UI must happen on the thread running the event loop. – millimoose Jan 21 '14 at 11:51
  • What I'd try to do is use `Task.Run()` and the `async..await` feature here, but I'm unfamiliar with it and don't have time to prepare and test code for an answer right now, so you'll have to make do with just the suggestion. – millimoose Jan 21 '14 at 12:41
  • I have edited your title. Please see, "[Should questions include “tags” in their titles?](http://meta.stackexchange.com/questions/19190/)", where the consensus is "no, they should not". – John Saunders Jan 22 '14 at 03:52

1 Answers1

2

I presume you're unable to use use ASP.NET 4.5 and async/await, which might be an ideal solution to this case (see this and this for more info).

However, with ASP 4.0 you still can use asynchronous code on your page with PageAsyncTask and Page.RegisterAsyncTask. That would extend the life time of the request, until the async task has completed:

PageAsyncTask asyncTask = new PageAsyncTask(
    slowTask.OnBegin, slowTask.OnEnd, slowTask1.OnTimeout, 
    "AsyncTaskName", true);

Then you invoke worker.BeginInvoke/EndInvoke from OnBegin/OnEnd. MSDN has a complete sample code of this.

To address the question itself:

Does this approach make sense? thank you for any information.

I don't think it makes sense, if I understood your code correctly.

Apparently, what you're doing here is starting a background thread and doing some synchronous operation on this thread, like Thread.Sleep(1000) inside your DoSend(). That would make sense for a client side UI app (to keep the UI responsive).

However, for your server-side app, you're not getting any advantage: at least one thread (the one started with BeginInvoke) remains blocked during your ongoing operation.

You might as well call DoSend() directly on the original thread that handles the HTTP request (inside btnSend_Click), without BeginInvoke. In that case, your app might even be scaling slightly better, because it'd have less thread switching.

Now, perhaps you can re-factor your code to use pure asynchronous IO/Network-bound APIs which do not require a dedicated thread. That's what would make your app scale much better. Read Stephen Cleary's There Is No Thread blog post, to get a better picture of what I'm talking about.

Community
  • 1
  • 1
noseratio
  • 59,932
  • 34
  • 208
  • 486