Sending multiple async emails - c#

I want to send multiple emails asynchronously. Emails do not depend on each other and no code will run after emails send, so i want to fire multiple and forget. I am following this article for sending async emails but i am not sure if i am doing the right thing for sending multiple emails.
A few of my concerns;
Sending of second email should not wait for first one.
Should i use QueueBackgroundWorkItem for each email sending task or use one QueueBackgroundWorkItem?
public class MyController : Controller
{
public async Task SendEmailAsync(IdentityMessage message)
{
var mailMessage = new MailMessage
("abc#domain.com", message.Destination, message.Subject, message.Body);
using(var client = new SmtpClient())
{
await client.SendMailAsync(mailMessage);
}
}
public ActionResult Create()
{
Action<CancellationToken> workItem = SendEmails;
HostingEnvironment.QueueBackgroundWorkItem(workItem);
return View();
}
private async void SendEmails(CancellationToken cancellationToken)
{
await SendEmailAsync(new IdentityMessage(...));
await SendEmailAsync(new IdentityMessage(...));
}
}

In SendEmails every next email is waiting for completion of previous, you should rewrite it like this:
private async Task SendEmails(CancellationToken cancellationToken)
{
await Task.WhenAll(SendEmailAsync(new IdentityMessage(...)),
SendEmailAsync(new IdentityMessage(...)));
}
By doing so all email will be send in parallel.
Also you should not QueueBackgroundWorkItem in Create. You can just do next:
public async Task<ActionResult> Create()
{
HostingEnvironment.QueueBackgroundWorkItem(async (token)=> await SendEmails() );
return View();
}
But please keep in mind that if any exception occur in SendEmails you will not be notified about this. Let me know if this can be an issue for you, so i will provide solution for this.
But i prefer another approach of sending emails from web application. It is more complicated but more reliable. Web app should never send messages to smtp server but store request to queue or db. Than we have background application that listen to queue and every time new request was detected it try to send it to smtp. if attempt fail system will return request back to the queue and will try few more times later. This background app may be windows service of webjob if your app is hosted in azure.
Advantages of this approach:
no UI thread blocking - to put something in queue is short running operation
even if smtp server is not accessible for some period of time you are sure that all emails will be delivered
Disadvantages:
You have to maintain additional component of your system

Related

Correct way to implement fire and forget async method call

In my project, I have a network call to sending Email, I don't want to wait for the response, because the most likely Email provider sends the Emails successfully. Which of the following methods is better and what is the difference?
Method 1: await SendAsync and use Task.Run
public async Task<IActionResult> Index()
{
await SendAsync();
return View();
}
private Task SendAsync()
{
_ = Task.Run(async () =>
{
_logger.LogInformation("Before");
await Task.Delay(10000); // Email send
_logger.LogInformation("After");
});
return Task.CompletedTask;
}
Method 2: Does not await SendAsync
public IActionResult Index()
{
SendAsync();
return View();
}
private async Task SendAsync()
{
_logger.LogInformation("Before");
await Task.Delay(10000); // Email send
_logger.LogInformation("After");
}
Both methods work with awaiting in Task.Delay(10000); line
In my project, I have a network call to sending Email, I don't want to wait for the response, because the most likely Email provider sends the Emails successfully.
Most email providers work by having you send into a queue, and then the actual email is sent later, when they process work out of that queue. So, sending into the queue is fast and very reliable.
So this means that your SendEmailAsync or whatever API should be quite fast, and shouldn't require returning early. Since SendEmailAsync actually sends to a queue, the only thing it represents is "please accept my request to send this email".
Which of the following methods is better and what is the difference?
Neither.
Since sending emails is just a queue write, and you don't want your request to be lost, the appropriate approach is not to use fire-and-forget at all:
public async Task<IActionResult> Index()
{
await SendAsync();
return View();
}
private async Task SendAsync()
{
_logger.LogInformation("Before");
await Task.Delay(10000); // Email send
_logger.LogInformation("After");
}
If, for some reason, you're using an email provider that doesn't queue, then you can create your own queue (Azure Storage Queue, Amazon SQS, RabbitMQ, etc) and change SendAsync to write a message to that queue. Then have a separate background process (Azure Function, Amazon Lambda, etc) read from that queue and send the emails to the email provider.
Both are equivalent. Even though there are many who advise against using this pattern - if you know what you are doing (like logging, taking care to handle all exceptions within the task, taking care that the background task behaves correctly during application shutdown etc.), from my experience it is actually ok to use this pattern, at least in .NET Core.
There are cases where it's not possible or practical or feasible (performance-wise) to handle the background task in a persistent manner (with a DB or a queue).
In fact, Microsoft's implementation of BackgroundService works exactly like that:
public virtual Task StartAsync(CancellationToken cancellationToken)
{
// Store the task we're executing
_executingTask = ExecuteAsync(_stoppingCts.Token);
// If the task is completed then return it, this will bubble cancellation and failure to the caller
if (_executingTask.IsCompleted)
{
return _executingTask;
}
// Otherwise it's running
return Task.CompletedTask;
}
Here, ExecuteAsync is an abstract Task (to be overriden by the subclass) that is run without await. The task is stored in an instance field just so it can be cancelled with a CancellationToken.

Respond to MailGun's HTTP post and then process the message

When receiving mail through MailGun they require a response within a limited time. I have two issues with this:
1) After receiving the message I need to process and record it in my CRM which takes some time. This causes MailGun to time out before I get to send a response. Then MailGun resends the message again and again as it continues to time out.
2) MailGun's post is not async but the api calls to my CRM are async.
So I need to send MailGun a 200 response and then continue to process the message. And that process needs to be in async.
The below code shows what I want to have happen. I tried using tasks and couldn't get it working. There are times when many emails can come in a once (like when initializing someone's account) if the solution requires some sort of parallel tasks or threads it would need to handle many of them.
public class HomeController : Controller
{
[HttpPost]
[Route("mail1")]
public ActionResult Mail()
{
var emailObj = MailGun.Receive(Request);
return Content("ok");
_ = await CRM.SendToEmailApp(emailObj);
}
}
Thank you for the help!
The easiest way to do what you are describing (which is not recommended, because you may lose some results if your app crash) is to use a fire & forget task:
var emailObj = MailGun.Receive(Request);
Task.Run(async () => await CRM.SendToEmailApp(emailObj));
return Content("ok");
But, I think what you really want is sort of a Message Queue, by using a message queue you put the message in the queue (which is fast enough) and return immediately, at the same time a processor is processing the message queue and saves the result in the CRM.
This is what it'll look like when you use a message queueing broker.

Receiving a Task<T> from another process on same machine

I have two internal processes which I use to upload long sdos strings to an API. Process 1 reads these from another stream. Process 1 (client) sends strings to process 2 (server) via a [ServiceContract] and a [MessageContract]. Process 2 then sends this to an API which in turn processes the sdos and uploads to a server.
[MessageContract]
public class CallRequestMessage
{
[MessageHeader]
public string Sdos;
[MessageHeader]
public int ArrayLength;
[MessageBodyMember]
public Stream SdosStream;
}
[MessageContract]
public class CallResponseMessage
{
[MessageHeader]
public Task<ResultCode> Task;
}
Since the bulk of the time processing the string is in the API, I want to try and return a Task<ResultCode> from my server that will get a result from the API once the processing has concluded. Then my threads can work on client-side processing (in this case, reading the sdos strings from a stream input).
My problem is that the tasks returned to the client seem to be different to the ones that I create on the server. On the server I have the code
task = Task<ResultCode>.Factory.StartNew(() =>
{
ResultCode res;
lock (SyncObject)
res = upload(/* input */)
return res;
});
// ...other code
return new CallResponseMessage { Task = task };
where upload is a method in the API, accessed by process 2 by using a [DllImportAttribute].
Using logs I have seen that the task does complete on the server (all sdos are uploaded), however on the client side, all tasks appear to not have started, and so retrieving the results is not possible directly.
An alternative approach that I thought of would be to return nothing from the server, and add a separate method that retrospectively goes to the server, awaits the tasks, and returns an aggregated result. I would like to try and get the task back directly, though, as this implementation may be a model for future services in my projects.
Thank you for any help.
There are no Task instances across process boundaries. The server's task is the Task that sends the data to the client. The client task is the task that receives the data. If you use the asnyc methods on the auto-generated WCF clients, by default, WCF will not stream the data from server to client, so your normal flow will be:
Start client task -> Send request -> Start server task -> End server task -> Send response -> End client task
In order for the server tasks to be performed asynchronously, you can design your service methods with the task asynchronous pattern (TAP). This example is from the official documentation:
public class SampleService:ISampleService
{
// ...
public async Task<string> SampleMethodTaskAsync(string msg)
{
return Task<string>.Factory.StartNew(() =>
{
return msg;
});
}
// ...
}
The benefits of tasks on client and server is not so much that the client can receive while the server sends the data, but to allow the server to process more incoming requests while other requests are waiting for long running operations (e.g. data access) and the client to do something useful while the data is received.
Your options are:
Use seperate asynchronous server and client operations
Unless you are transferring large amounts of data and performance is critical, there is nothing wrong with the situation. You can still use tasks for async programming. However, your approach of returning a task won't work. Use the described combination of async service methods and the auto-generated async client methods. You will essentially achieve the same result, which is that both, client and server will perform the operation asynchronously.
Stream the data
If you must start processing on the client while the server is sending the data (which only brings you a practical benefit for large amounts of data), you can stream the data from the server. This issue is too large to cover here, but a good point to start is the official documentation.

WebApi how to send asyncronous email after returning response to user?

Emails are taking too long to send (sometimes up to 7-8) seconds.
I want to send a response back to my clients without them having to wait for the email to send. Is this possible?
public async Task<IHttpActionResult> Action()
{
//Do something
await email.sendAsync(); //Can take up to 10 seconds...
return Ok();
}
I can remove the await, but I obviously get a An asynchronous module or handler completed while an asynchronous operation was still pending
How can I achieve this?
Ideally you would want to offload this work onto some other process, maybe a queue and then have some subscribers process off that queue to send the email but that is a lot of work and we live in the real world.
You can use
HostingEnvironment.QueueBackgroundWorkItem(ct => email.sendAsync());
to get something quite reliable up and running.

Asynchronous operation cannot be started at this time

I am here just trying to download the emails in "INBOX" from the server using IMAP with ComponentPro .net component (Asynchronous Task-Based Approach) but it throws the error every time when I try Asynchronous method.
I already got Inbox emails downloaded using IMAP with Synchronous approach but this takes nearly 3-4 minutes to download atleast 80+ emails, So I want to try asynchronous approach any suggestions on this as I am trying asynchronous approach for the first time.
Error:
An asynchronous operation cannot be started at this time. Asynchronous operations may only be started within an asynchronous handler or module or during certain events in the Page lifecycle.
Controller Code:
public ActionResult ImportEmailDemo()
{
var sImportedEmails = ARepository.ImportEmailForDemo();
return null;
}
Repository Code:
public async Task<string> ImportEmailForDemo()
{
//Async
// Create a new instance of the Imap class.
Imap client1 = new Imap();
// Connect to the server.
client1.Connect("Server Address");
// Or you can specify the IMAP port with
// client.Connect("myserver", 143);
// Login to the server.
client1.Authenticate("EmailID", "Password");
// Select 'INBOX' mailbox.
client1.Select("INBOX");
// Download a mail message with sequence number 1.
ComponentPro.Net.Mail.MailMessage msg = await client1.DownloadMailMessageAsync(1);
// ...
Console.WriteLine("Message downloaded successfully.");
Console.WriteLine("Message ID: {0}, Subject: {1}", msg.MessageIdentifier, msg.Subject);
// Disconnect.
client1.Disconnect();
return null;
}
I tried with different ways to resolve the error looking in internet but nothing worked for me, Can any one help me out.
Cheers!!
I already got Inbox emails downloaded using IMAP with Synchronous approach but this takes nearly 3-4 minutes to download atleast 80+ emails, So I want to try asynchronous approach
Asynchronous won't be any faster.
An asynchronous operation cannot be started at this time.
This error occurs when your code starts an asynchronous operation within a synchronous handler. You should not be using Wait or Result; instead, you need to await the returned task, which makes that method async, etc., until your handler is changed to be asynchronous.
But as noted above, using async is not going to make it faster anyway.

Categories

Resources