I'm trying to send 2 emails through the SendGrid API. Sometimes 0 send, sometimes 1 sends, sometimes both send. It seems that the function does not await the promise. How can I fix it so it always sends both emails?
My function looks like this:
private async Task<bool> SendMails(string email, string name, string pdfPath, string imgPath)
{
var client = new SendGridClient(_config["SendGrid:Key"]);
bool messagesSent = false;
var messageClient = new SendGridMessage
{
From = new EmailAddress(_config["SendGrid:Recipient"]),
Subject = "Testmail",
HtmlContent = _textManager.Get("getMailHtml")
};
var messageSecondClient = new SendGridMessage
{
From = new EmailAddress(_config["SendGrid:Recipient"]),
Subject = "Second Testmail",
HtmlContent = _textManager.Get("getSecondMailHtml")
};
messageClient.AddTo(email, name);
messageSecondClient.AddTo(email, name);
string[] fileListClient = new string[] { pdfPath };
string[] fileListSecond = new string[] { pdfPath, imgPath };
foreach (var file in fileListClient)
{
var fileInfo = new FileInfo(file);
if (fileInfo.Exists)
await messageClient.AddAttachmentAsync(fileInfo.Name, fileInfo.OpenRead());
}
foreach (var file in fileListSecond)
{
var fileInfo = new FileInfo(file);
if (fileInfo.Exists)
await messageSecondClient.AddAttachmentAsync(fileInfo.Name, fileInfo.OpenRead());
}
var responseClient = await client.SendEmailAsync(messageClient);
var responseSecond = await client.SendEmailAsync(messageSecondClient);
if (responseClient.StatusCode.ToString() == "202" && responseSecond.StatusCode.ToString() == "202")
{
messagesSent = true;
}
return messagesSent;
}
And this is how I'm calling it:
Task<bool> sendMails = await Task.FromResult(SendMails(formCollection["email"], formCollection["name"], pdfPath, imgPath));
if (!sendMails.Result)
{
errorMessage = "Error sending mails.";
}
You're blocking on the async task:
if (!sendMails.Result)
and this can cause a deadlock. Instead of blocking, use await.
And you can also get rid of the await Task.FromResult, which isn't doing anything at all:
bool sentMails = await SendMails(formCollection["email"], formCollection["name"], pdfPath, imgPath);
if (!sentMails)
{
errorMessage = "Error sending mails.";
}
Task.FromResult returns a new Task that is already completed, not the Task returned from SendMails.
Nothing is awaiting the completion of SendMails.
Just await the Task returned from the method:
bool result = await SendMails(formCollection["email"], formCollection["name"], pdfPath, imgPath);
The await keyword unwraps the Task.Result for you.
Related
How to run an async Task without blocking other tasks?
I have one function that iterates though a List but the problem is that when the function is called other functions won't work again until the first function is done. What are the ways of making the HandleAsync function non-blocking ?
public static async Task HandleAsync(Message message, TelegramBotClient bot)
{
await Search(message, bot); // This should be handled without working other possible functions. I have a function similar to this but which doesn't iterate though any list.
}
private static async Task Search(Message message, TelegramBotClient bot)
{
var textSplit = message.Text.Split(new[] {' '}, 2);
if (textSplit.Length == 1)
{
await bot.SendTextMessageAsync(message.From.Id, "Failed to fetch sales. Missing game name. ",
ParseMode.Html);
}
else
{
var search = await Program.itad.SearchGameAsync(textSplit[1], limit: 10, cts: Program.Cts);
if (search.Data != null)
{
var builder = new StringBuilder();
foreach (var deal in search.Data.List)
{
var title = deal.Title;
var plain = deal.Plain;
var shop = deal.Shop != null ? deal.Shop.Name : "N/A";
var urls = deal.Urls;
var priceNew = deal.PriceNew;
var priceOld = deal.PriceOld;
var priceCut = deal.PriceCut;
builder.AppendLine($"<b>Title:</b> {title}");
builder.AppendLine($"<b>Shop:</b> {shop}");
builder.AppendLine();
builder.AppendLine($"<b>Price:</b> <strike>{priceOld}€</strike> | {priceNew}€ (-{priceCut}%)");
var buttons = new[]
{
new[]
{
InlineKeyboardButton.WithUrl("Buy", urls.Buy.AbsoluteUri),
InlineKeyboardButton.WithUrl("History",
urls.Game.AbsoluteUri.Replace("info", "history"))
}
};
var keyboard = new InlineKeyboardMarkup(buttons);
var info = await Program.itad.GetInfoAsync(plain, cts: Program.Cts);
var image = info.Data.GameInfo.Image;
if (image == null) image = new Uri("https://i.imgur.com/J7zLBLg.png");
await TelegramBot.Bot.SendPhotoAsync(message.From.Id, new InputOnlineFile(image.AbsoluteUri),
builder.ToString(), ParseMode.Html, replyMarkup: keyboard,
cancellationToken: Program.Cts.Token);
builder.Clear();
}
}
else
{
await bot.SendTextMessageAsync(message.From.Id, "Failed to fetch sales. Game not found. ",
ParseMode.Html);
}
}
Had this problem yesterday, changed some code, and it's happening again... Why don't both mails send? The promise is not awaited for. Sometimes 1, sometimes 2 mails send. Bool "messagesSent" also returns false sometimes, when it should return true.
The function:
private async Task<bool> SendMails(string email, string name, string pdfPath, string imgPath)
{
var client = new SendGridClient(_config["SendGrid:Key"]);
bool messagesSent = false;
try
{
var messageClient = new SendGridMessage
{
From = new EmailAddress(_config["SendGrid:Recipient"]),
Subject = "Subject1",
HtmlContent = _textManager.Get("email-b")
};
var MessageServer = new SendGridMessage
{
From = new EmailAddress(_config["SendGrid:Recipient"]),
Subject = "Subject2",
HtmlContent = _textManager.Get("email-s")
};
messageClient.AddTo(email, name);
MessageServer.AddTo(email, name);
string[] fileListClient = new string[] { pdfPath };
string[] FileListServer = new string[] { pdfPath, imgPath };
foreach (var file in fileListClient)
{
var fileInfo = new FileInfo(file);
if (fileInfo.Exists)
await messageClient.AddAttachmentAsync(fileInfo.Name, fileInfo.OpenRead());
}
foreach (var file in FileListServer)
{
var fileInfo = new FileInfo(file);
if (fileInfo.Exists)
await MessageServer.AddAttachmentAsync(fileInfo.Name, fileInfo.OpenRead());
}
var responseClient = await client.SendEmailAsync(messageClient);
var responseServer = await client.SendEmailAsync(MessageServer);
if (responseClient.StatusCode.ToString() == "202" && responseServer.StatusCode.ToString() == "202")
{
messagesSent = true;
}
}
catch (Exception e)
{
Console.WriteLine(e);
}
return messagesSent;
}
Called by:
bool sentMails = await SendMails(formCollection["email"], formCollection["name"], pdfPath, imgPath);
if (!sentMails)
{
errorMessage = "Error sending mails.";
succes = false;
}
EDIT:
Request was valid, the call was being blocked by ISP/MailServer. This had nothing to do with faulty async.
Ok, the whole point of an asynchroneous call, is not to "wait" or delay performance. The method will just 'run'. Unless there is an exception raised in that method, there is nothing that would prevent the method from not get executed successfully.
Try adding a break-point and see why the messagesSent returns false sometimes, I am suspecting this line:
if (responseClient.StatusCode.ToString() == "202" && responseServer.StatusCode.ToString() == "202")
Is there any chance that the StatusCode might be returning a "200" as well?
Bot Info
SDK Platform: .NET
SDK Version: 3.14.0.7
Active Channels: Web
Deployment Environment: Local development with Emulator
Issue Description
We've trying to unit test every case that we have stored in a certain Dictionary, it seems to be working fine when the user sends and string and the test has to answer with a string. But we can't find any documentation on how to test the other kind of dialogs, like with attachments, buttons, etc.
We wish to make a dictionary of string,objects where the string is what we ask the bot and de object is either a string, Attachment, Dialog.
Code Example
This is how we store the answers:
public static Dictionary<string, object> data = new Dictionary<string, object>{
{"Nuevo", "Que quieres crear?"},
{"Ayuda", "Ya te ayudas!"},
{"Adios", "Nos vemos!"},
{
"Coche",
new Attachment() {
ContentUrl = "https://media.ed.edmunds-media.com/subaru/impreza/2006/oem/2006_subaru_impreza_sedan_sti_fq_oem_1_500.jpg",
ContentType = "image/png",
Name = "Subaru_Impreza.png"
}
},
{
"Moto",
new Attachment() {
ContentUrl = "http://motos.honda.com.co/sites/default/files/motos/cb-1000-r-cc-menu-honda.png",
ContentType = "image/png",
Name = "moto.png"
}
},
{
"Perro",
new Attachment() {
ContentUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/6/6b/Taka_Shiba.jpg/1200px-Taka_Shiba.jpg",
ContentType = "image/png",
Name = "ShibaInu.png"
}
}
};
This is how the bot works and returns everything, this is working as intended for at least text and attachments but we haven't done it for more type of messages.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
var r = context.MakeMessage();
foreach (var item in data)
{
if (item.Key == activity.Text)
{
if (item.Value is Attachment)
{
r.Attachments = new List<Attachment>() { item.Value as Attachment };
}
if (item.Value is string)
{
r.Text = item.Value.ToString();
}
break;
}
}
// return our reply to the user
await context.PostAsync(r);
context.Wait(MessageReceivedAsync);
}
But when we want to make the test for it, it only works when what we send is a string not a IMessageActivity, which works in the emulator.
The code for the test:
[TestMethod]
public async Task Pregunta_respuesta_prueba()
{
foreach (var item in RootDialog.data)
{
var preg = item.Key;
var resp = item.Value;
if (item.Value is Attachment)
{
Attachment auxText = resp as Attachment;
resp = auxText.ContentUrl;
}
using (ShimsContext.Create())
{
// Arrange
var waitCalled = false;
object message = null;
var target = new RootDialog();
var activity = new Activity(ActivityTypes.Message)
{
Text = preg
};
var awaiter = new Microsoft.Bot.Builder.Internals.Fibers.Fakes.StubIAwaiter<IMessageActivity>()
{
IsCompletedGet = () => true,
GetResult = () => activity
};
var awaitable = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIAwaitable<IMessageActivity>()
{
GetAwaiter = () => awaiter
};
var context = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIDialogContext();
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.PostAsyncIBotToUserStringStringCancellationToken = (user, s1, s2, token) =>
{
message = s1;
Console.WriteLine(message);
return Task.CompletedTask;
};
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.WaitIDialogStackResumeAfterOfIMessageActivity = (stack, callback) =>
{
if (waitCalled) return;
waitCalled = true;
// The callback is what is being tested.
callback(context, awaitable);
};
// Act
await target.StartAsync(context);
// Assert
Assert.AreEqual(resp, message);
}
}
}
If you check this part of the code
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.PostAsyncIBotToUserStringStringCancellationToken = (user, s1, s2, token) =>
{
message = s1;
Console.WriteLine(message);
return Task.CompletedTask;
};
```
It does only works when the bot is returning an string, we can't even check if it is an activiy, this happens because the Fake Context that we create for the test is not working as expected.
That IDialogContext that we are faking doesnt seem to work at all when it is an object, but it does work when it is a string.
private async Task MessageReceivedAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
/// Here when the test is running, this context.MakeMessage is null, but when the bot
/// is working, it wors perfectly.
var r = context.MakeMessage();
foreach (var item in data)
{
if (item.Key == activity.Text)
{
if (item.Value is Attachment)
{
r.Attachments = new List<Attachment>() { item.Value as Attachment };
}
if (item.Value is string)
{
r.Text = item.Value.ToString();
}
break;
}
}
// return our reply to the user
await context.PostAsync(r);
context.Wait(MessageReceivedAsync);
}
Reproduction Steps
To try this out you can try to test with an attachment, code is in this repository.
In stead of using PostAsyncIBotToUserStringStringCancellationToken, you can use context.PostAsyncIMessageActivityCancellationToken And, in the RootDialog's MessageReceivedWithTextAsync respond with an activity reply instead of just a string.
public async Task MessageReceivedWithTextAsync(IDialogContext context, IAwaitable<object> result)
{
var activity = await result as Activity;
string r = "";
foreach (var item in dataText)
{
if (item.Key == activity.Text)
{
r = item.Value;
break;
}
}
var reply = activity.CreateReply(r);
foreach (var item in dataAtt)
{
if (item.Key == activity.Text)
{
reply.Attachments.Add(item.Value);
reply.Text = "attachment";
break;
}
}
if ((string.IsNullOrWhiteSpace(r) || r == null) && reply.Attachments.Count == 0)
{
reply.Text = "No tengo respuesta para eso.";
}
// return our reply to the user
await context.PostAsync(reply);
}
Here are the changes to the test method:
[TestMethod]
public async Task Bot_Test_Attachments()
{
foreach (var item in RootDialog.dataAtt)
{
var preg = item.Key;
var att = item.Value;
using (ShimsContext.Create())
{
var waitCalled = false;
IMessageActivity message = null;
var target = new RootDialog();
var activity = new Activity(ActivityTypes.Message)
{
Text = preg,
From = new ChannelAccount("id","name"),
Recipient = new ChannelAccount("recipid","recipname"),
Conversation = new ConversationAccount(false,"id","name")
};
var awaiter = new Microsoft.Bot.Builder.Internals.Fibers.Fakes.StubIAwaiter<IMessageActivity>()
{
IsCompletedGet = () => true,
GetResult = () => activity
};
var awaitable = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIAwaitable<IMessageActivity>()
{
GetAwaiter = () => awaiter
};
var context = new Microsoft.Bot.Builder.Dialogs.Fakes.StubIDialogContext();
context.PostAsyncIMessageActivityCancellationToken = (messageActivity, token) => {
message = messageActivity;
return Task.CompletedTask;
};
Microsoft.Bot.Builder.Dialogs.Fakes.ShimExtensions.WaitIDialogStackResumeAfterOfIMessageActivity = (stack, callback) =>
{
if (waitCalled) return;
waitCalled = true;
callback(context, awaitable);
};
await target.MessageReceivedWithTextAsync(context, awaitable);
Assert.AreEqual(att, message.Attachments[0]);
}
}
}
I am working on bot technology, in one of my projet i wrote below lines of code
private async void DeliveryProgressReport(IDialogContext context, Activity message)
{
MessagesController.progressdetails = SQLDatabaseService.getinprogressdetails();
var progress = MessagesController.progressdetails;
if (progress.Count > 0)
{
try
{
Activity replyToConversation = message.CreateReply("**In Progress Report Details**");
replyToConversation.Recipient = message.From;
replyToConversation.Type = "message";
replyToConversation.Attachments = new List<Attachment>();
Dictionary<string, string> progresslist = new Dictionary<string, string>();
foreach (var progressreport in progress)
{
//Invoke the machine learning model for predicting the delivery status of delivery person
//var deliveryStatus= await InvokeRequestResponseServiceOfDeliveryPersonPredictionExp1();
//await Task.Delay(TimeSpan.FromSeconds(5));
var deliveryStatus = await InvokeRequestResponseServiceOfDeliveryPersonPredictionExp(progress[0].Name, progress[0].Mobile_Number);
progresslist.Add(progressreport.Name, progressreport.Mobile_Number);
List<CardImage> cardImages = new List<CardImage>();
cardImages.Add(new CardImage(url: progressreport.Photo_Url));
ThumbnailCard tlcard = new ThumbnailCard()
{
Title = "Name:" + progressreport.Name,
Subtitle = "Call:" + progressreport.Mobile_Number,
Images = cardImages,
Text = "Staus by Using Machine Learning Prediction:" + deliveryStatus
};
Attachment plAttachment = tlcard.ToAttachment();
replyToConversation.Attachments.Add(plAttachment);
}
replyToConversation.AttachmentLayout = AttachmentLayoutTypes.List;
await context.PostAsync(replyToConversation);
} catch (Exception ex)
{
Debug.WriteLine(ex.Message);
}
}
else
{
Activity replyToConversation = message.CreateReply("**There are no in progress deliveries are found**");
await context.PostAsync(replyToConversation);
}
}
private async Task<string> InvokeRequestResponseServiceOfDeliveryPersonPredictionExp(string name, string mobile_Number)
{
string status = "";
//Func<Stream, Task> copyStreamAsync = async stream =>
//{
//await Task.Factory.StartNew(async () =>
//{
//using (stream)
//using (var sourceStream = await sourceContent.Content.ReadAsStreamAsync())
//{
// await sourceStream.CopyToAsync(stream);
//}
//var client = new HttpClient();
using (var client = new HttpClient())
{
var scoreRequest = new
{
Inputs = new Dictionary<string, StringTable>() {
{
"input1",
new StringTable()
{
ColumnNames = new string[] {"Id", "Name", "Mobile_Number", "CourierCompany_Name", "Status", "EmailId"},
Values = new string[,] { { "", name, mobile_Number, "", "","" }, { "", name, mobile_Number, "", "", "" }, }
}
},
},
GlobalParameters = new Dictionary<string, string>()
{
}
};
const string apiKey = "xxxxxxxxxxxxxxxxxxxxxxxxxxxx=="; // Replace this with the API key for the web service
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", apiKey);
client.BaseAddress = new Uri("My Request URL");
// WARNING: The 'await' statement below can result in a deadlock if you are calling this code from the UI thread of an ASP.Net application.
// One way to address this would be to call ConfigureAwait(false) so that the execution does not attempt to resume on the original context.
// For instance, replace code such as:
// result = await DoSomeTask()
// with the following:
// result = await DoSomeTask().ConfigureAwait(false)
//var status = await PostRequest(scoreRequest,client).ConfigureAwait(false);
HttpResponseMessage response = await client.PostAsJsonAsync("", scoreRequest);//.ConfigureAwait(false);
string correctLocation = "";
string wrongLocation = "";
string notReached = "";
string personMismatch = "";
if (response.IsSuccessStatusCode)
{
string result = await response.Content.ReadAsStringAsync();
var results = JsonConvert.DeserializeObject<RootObject>(result);
foreach (var value in results.Results.output1.value.Values)
{
status = value[8].ToString();
correctLocation = value[4].ToString();
notReached = value[5].ToString();
personMismatch = value[6].ToString();
wrongLocation = value[7].ToString();
}
Debug.WriteLine("Result: {0}", result);
return status;
}
else
{
Debug.WriteLine(string.Format("The request failed with status code: {0}", response.StatusCode));
// Print the headers - they include the requert ID and the timestamp, which are useful for debugging the failure
Debug.WriteLine(response.Headers.ToString());
string responseContent = await response.Content.ReadAsStringAsync();
Debug.WriteLine(responseContent);
return status;
}
};
// return status;
}
After executing this below line I got the exception like asynchronous module or handler completed while an asynchronous operation was still pending
await context.PostAsync(replyToConversation);
Before posting this question I had followed through this below links, but I didn't resolve it.
Async Void, ASP.Net, and Count of Outstanding Operations
Web Api + HttpClient: An asynchronous module or handler completed while an asynchronous operation was still pending
Please tell how to resolve this exception.
-Pradeep
Finally, I resolved the above exception when I am return Task instead of void in DeliveryProgressReport method. and also where ever I was called the await DeliveryProgressReport() method there also I return Task instead of void.
-Pradeep
I have a the following controller method
public async Task<ActionResult> SendToAllUsers(SentMailToAllUsersModel model)
{
if (ModelState.IsValid)
{
var mail = MailService.SendMailToAllUsers(model.Body, model.Title);
await mail;
}
return View(model);
}
Which is calling this method on the mail service
public Task SendMailToAllUsers(string content, string title)
{
var users = UserService.GetAllUsers();
var mailTemplates = users.Result.AsParallel().Select(user =>
{
var mailTemplate = new MastersMailTemplate(user);
mailTemplate.HtmlEmailTemplate = content;
mailTemplate.Subject = title;
mailTemplate.From = _fromEmail;
return Task.Factory.StartNew(() => MailProvider.SendEmailAsync(mailTemplate.CreateMailMessage(), new ResultDescription()).ConfigureAwait(false));
}).ToArray();
return Task.WhenAll(mailTemplates);
}
This method is triggering the mail provider that executes this method:
public Task<IResultDescription> SendEmailAsync(MailMessage message, IResultDescription rd)
{
// Create our SMTP Client
SmtpClient client = new SmtpClient();
client.Host = SmtpServer;
client.Port = SmtpServerPort;
client.Credentials = new NetworkCredential(SmtpServerUsername, SmtpServerPassword);
client.EnableSsl = true;
if (AppSettings.IsInTestMode)
{
Log.Info("Test mode check: Removing all emails and replace to test");
message.To.Clear();
foreach (var email in AppSettings.DefaultTestEmail)
{
message.To.Add(new MailAddress(email));
}
}
client.Timeout = 10;
Log.Info("Sending Email to" + message.To.FirstOrDefault());
var task = Task.Run(async () =>
{
try{
client.SendCompleted += (s, e) =>
{
client.Dispose();
message.Dispose();
};
await client.SendAsync(message);
rd.Success = true;
return rd;
}
catch (Exception e)
{
Log.Error("Email not send");
rd.Success = false;
if (rd.Errors == null)
{
IList<string> errors = new List<string>();
errors.Add(e.Message);
rd.Errors = errors;
}
else
{
rd.Errors.Add(e.Message);
}
return rd;
}
});
return task;
}
The problem is that the result view is returned before any mails where sent.
The controller is not waiting untill all mails are sent.
How can I make sure that the controller only continues execution when all tasks in the mail service are completed?
As a general rule, do not use Task.Run, Task.Factory.StartNew, Parallel, or PLINQ on ASP.NET. There is always a better way. In this case, just use async and await:
public async Task SendMailToAllUsersAsync(string content, string title)
{
var users = await UserService.GetAllUsersAsync();
var mailTemplates = users.AsParallel().Select(user =>
{
var mailTemplate = new MastersMailTemplate(user);
mailTemplate.HtmlEmailTemplate = content;
mailTemplate.Subject = title;
mailTemplate.From = _fromEmail;
return MailProvider.SendEmailAsync(mailTemplate.CreateMailMessage());
}).ToArray();
return await Task.WhenAll(mailTemplates);
}
Similarly for your inner method:
public Task<IResultDescription> SendEmailAsync(MailMessage message, IResultDescription rd)
{
using (SmtpClient client = new SmtpClient())
using (message)
{
client.Host = SmtpServer;
client.Port = SmtpServerPort;
client.Credentials = new NetworkCredential(SmtpServerUsername, SmtpServerPassword);
client.EnableSsl = true;
if (AppSettings.IsInTestMode)
{
Log.Info("Test mode check: Removing all emails and replace to test");
message.To.Clear();
foreach (var email in AppSettings.DefaultTestEmail)
{
message.To.Add(new MailAddress(email));
}
}
client.Timeout = 10;
Log.Info("Sending Email to" + message.To.FirstOrDefault());
try
{
await client.SendAsync(message);
rd.Success = true;
}
catch (Exception e)
{
Log.Error("Email not send");
rd.Success = false;
if (rd.Errors == null)
{
IList<string> errors = new List<string>();
errors.Add(e.Message);
rd.Errors = errors;
}
else
{
rd.Errors.Add(e.Message);
}
}
return rd;
}
}
Remember, async makes things easy. If the async code is excessively complicated, check for A Better Way. I have an async intro on my blog that you may find useful.
I think the problem is in your SendMailToAllUsers method. I think you need to await the MailProvider.SendEmailAsync call. If you don't do this, the task started by Task.Factory.StartNew will be considered complete as soon as that method executes. Because the method is actually asynchronous it only kicks off the operation, it doesn't wait for its completion. If you await the result that should fix the problem.
Change your code to:
public Task SendMailToAllUsers(string content, string title)
{
var users = UserService.GetAllUsers();
var mailTemplates = users.Result.AsParallel().Select(user =>
{
var mailTemplate = new MastersMailTemplate(user);
mailTemplate.HtmlEmailTemplate = content;
mailTemplate.Subject = title;
mailTemplate.From = _fromEmail;
// Await the result of the lambda expression
return Task.Factory.StartNew(() => await MailProvider.SendEmailAsync(mailTemplate.CreateMailMessage(), new ResultDescription()).ConfigureAwait(false));
}).ToArray();
return Task.WhenAll(mailTemplates);
}