C# httpclient hangs at the last request - c#

I have a program which send a couple of thousands of post requests to the server. Everything is working fine until the end of the transmission. After all but one request are sent the program hangs.
Always there is only one request left to make. I am using HttpClient and everything is done async.
Here I create the httpclient:
ServicePointManager.ServerCertificateValidationCallback +=
(sender, cert, chain, sslPolicyErrors) => true;
ServicePointManager.DefaultConnectionLimit = 45;
ServicePointManager.Expect100Continue = false;
var CurrentCredentials = new System.Net.NetworkCredential(Repo.Username, Repo.Password);
var Handler = new HttpClientHandler
{
Credentials = CurrentCredentials,
ClientCertificateOptions = ClientCertificateOption.Automatic,
UseProxy = false,
};
var Client = new HttpClient(Handler);
Client.Timeout = TimeSpan.FromSeconds(2500);
Client.DefaultRequestHeaders.Add("Connection", "Keep-alive");
WebServ = new WebService(Client, ref Repo);
Here is where I send the requests:
private async Task<string> SendData(string Url, HttpContent[] content, string[] name)
{
using (var FormData = new MultipartFormDataContent())
{
for (int i = 0; i < content.Length; ++i)
{
if (name[i] == "file")
FormData.Add(content[i], name[i], name[i] + i.ToString() + ".jpg");
else
FormData.Add(content[i], name[i]);
}
try
{
using (var Response = await Client
.PostAsync(Url, FormData)
.ConfigureAwait(continueOnCapturedContext:false))
{
await App.Current.Dispatcher.BeginInvoke(
(Action)delegate
{
Repo.CurrentItem++;
});
using (var StreamResume = new StreamWriter("resume.bkp"))
{
await StreamResume.WriteLineAsync(Repo.CurrentItem.ToString());
}
if (Response.IsSuccessStatusCode)
{
await App.Current.Dispatcher.BeginInvoke(
(Action)delegate
{
Repo.Log.Add(Url + " OK" + Repo.CurrentItem);
});
}
else
{
await App.Current.Dispatcher.BeginInvoke(
(Action)delegate
{
Repo.Log.Add(Url + " Error "
+ Response.Content.ToString());
});
}
if (Repo.CurrentItem == Repo.NumberOfItems)
{
await App.Current.Dispatcher.BeginInvoke(
(Action)delegate
{
Repo.Log.Add("Data has been imported");
Repo.CurrentState = "Finished!";
Repo.CurrentItem = 0;
Repo.Uploading = false;
Repo.NotUploading = true;
Repo.Resumable = false;
File.Delete("resume.bkp");
});
}
}
return null;
}
catch (OperationCanceledException)
{
}
catch (InvalidOperationException)
{
App.Current.Dispatcher.Invoke(
(Action)delegate
{
Repo.Log.Add("The url is not valid");
Repo.CurrentItem = 0;
});
Client.CancelPendingRequests();
}
return null;
}
}
I encounter no errors whatsoever only that some threads never exit and the program never terminates. If I have a smaller data set, where only about 180 requests are made, the program does the job never hanging. Thanks in advance for your help

Related

Return success/error response after making some graph calls

I have the following code to add users to AAD group:
public async Task AddUsersToGroup(IEnumerable<BatchRequestContent> batches)
{
try
{
foreach (var batchRequestContent in batches)
{
var response = await _graphServiceClient
.Batch
.Request()
.WithMaxRetry(10)
.PostAsync(batchRequestContent);
var responses = await response.GetResponsesAsync();
foreach (string key in responses.Keys)
{
var httpResponse = await response.GetResponseByIdAsync(key);
httpResponse.EnsureSuccessStatusCode();
}
}
}
catch (Exception ex)
{
await _log.LogMessageAsync(new LogMessage
{
Message = ex.GetBaseException().ToString(),
RunId = RunId
});
throw;
}
}
I need to update this method to return 'Ok' if all the requests returned a success response, else return 'Error' if any of the requests returned an error response. How would I do that? Please help!
private IEnumerable<BatchRequestContent> GetBatchRequest(IEnumerable<AzureADUser> users, AzureADGroup targetGroup)
{
var batches = new List<BatchRequestContent>();
int maxNoBatchItems = 20;
var batchRequestContent = new BatchRequestContent();
int requestId = 1;
foreach (var user in users)
{
JObject body = new JObject
{
["members#odata.bind"] = $"https://graph.microsoft.com/v1.0/users/{user.ObjectId}"
};
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Patch, $"https://graph.microsoft.com/v1.0/groups/{targetGroup.ObjectId}");
httpRequestMessage.Content = new StringContent(body.ToString(), Encoding.UTF8, "application/json");
batchRequestContent.AddBatchRequestStep(new BatchRequestStep(requestId.ToString(), httpRequestMessage));
if (batchRequestContent.BatchRequestSteps.Count() % maxNoBatchItems == 0)
{
batches.Add(batchRequestContent);
batchRequestContent = new BatchRequestContent();
}
requestId++;
}
if (batchRequestContent.BatchRequestSteps.Count < maxNoBatchItems)
{
batches.Add(batchRequestContent);
}
return batches;
}

Bing Speech to Text API - Communicate via websocket in c#

I'm trying to get the Bing Speech API to work in C# via WebSockets. I've looked through the implementation in Javascript here and have been following the protocol instructions here, but I've come up against a complete brick wall. I can't use the existing C# service because I'm running in a Linux container, so I need to use an implementation on .net Core. Annoyingly, the existing service is closed-source!
I can connect to the web socket successfully, but I can't ever get the server to respond to my connection. I'm expecting to receive a turn.start text message from the server, but I get booted off the server as soon as I've sent a few bytes of an audio file. I know the audio file is in the right format because I've got it directly from the C# service sample here.
I feel like I’ve exhausted the options here. The only thing I can think of now is that I’m not sending the audio chunks correctly. Currently, I’m just sending the audio file in consecutive 4096 bytes. I know the first audio message contains the RIFF header which is only 36 bytes, and then I'm just sending this along with the next (4096-36) bytes.
Here is my code in full. You should just be able to run it as a .net core or .net framework console application, and will need an audio file and an API key.
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Net.Http;
using System.Net.WebSockets;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Task.Run(async () =>
{
var bingService = new BingSpeechToTextService();
var audioFilePath = #"FILEPATH GOES HERE";
var authenticationKey = #"BING AUTHENTICATION KEY GOES HERE";
await bingService.RegisterJob(audioFilePath, authenticationKey);
}).Wait();
}
}
public class BingSpeechToTextService
{
/* #region Private Static Methods */
private static async Task Receiving(ClientWebSocket client)
{
var buffer = new byte[128];
while (true)
{
var result = await client.ReceiveAsync(new ArraySegment<byte>(buffer), CancellationToken.None);
var res = Encoding.UTF8.GetString(buffer, 0, result.Count);
if (result.MessageType == WebSocketMessageType.Text)
{
Console.WriteLine(Encoding.UTF8.GetString(buffer, 0, result.Count));
}
else if (result.MessageType == WebSocketMessageType.Close)
{
Console.WriteLine($"Closing ... reason {client.CloseStatusDescription}");
var description = client.CloseStatusDescription;
//await client.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", CancellationToken.None);
break;
}
else
{
Console.WriteLine("Other result");
}
}
}
/* #endregion Private Static Methods */
/* #region Public Static Methods */
public static UInt16 ReverseBytes(UInt16 value)
{
return (UInt16)((value & 0xFFU) << 8 | (value & 0xFF00U) >> 8);
}
/* #endregion Public Static Methods */
/* #region Interface: 'Unscrypt.Bing.SpeechToText.Client.Api.IBingSpeechToTextJobService' Methods */
public async Task<int?> RegisterJob(string audioFilePath, string authenticationKeyStr)
{
var authenticationKey = new BingSocketAuthentication(authenticationKeyStr);
var token = authenticationKey.GetAccessToken();
/* #region Connect web socket */
var cws = new ClientWebSocket();
var connectionId = Guid.NewGuid().ToString("N");
var lang = "en-US";
cws.Options.SetRequestHeader("X-ConnectionId", connectionId);
cws.Options.SetRequestHeader("Authorization", "Bearer " + token);
Console.WriteLine("Connecting to web socket.");
var url = $"wss://speech.platform.bing.com/speech/recognition/interactive/cognitiveservices/v1?format=simple&language={lang}";
await cws.ConnectAsync(new Uri(url), new CancellationToken());
Console.WriteLine("Connected.");
/* #endregion*/
/* #region Receiving */
var receiving = Receiving(cws);
/* #endregion*/
/* #region Sending */
var sending = Task.Run(async () =>
{
/* #region Send speech.config */
dynamic speechConfig =
new
{
context = new
{
system = new
{
version = "1.0.00000"
},
os = new
{
platform = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/59.0.3071.115 Safari/537.36",
name = "Browser",
version = ""
},
device = new
{
manufacturer = "SpeechSample",
model = "SpeechSample",
version = "1.0.00000"
}
}
};
var requestId = Guid.NewGuid().ToString("N");
var speechConfigJson = JsonConvert.SerializeObject(speechConfig, Formatting.None);
StringBuilder outputBuilder = new StringBuilder();
outputBuilder.Append("path:speech.config\r\n"); //Should this be \r\n
outputBuilder.Append($"x-timestamp:{DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffK")}\r\n");
outputBuilder.Append($"content-type:application/json\r\n");
outputBuilder.Append("\r\n\r\n");
outputBuilder.Append(speechConfigJson);
var strh = outputBuilder.ToString();
var encoded = Encoding.UTF8.GetBytes(outputBuilder.ToString());
var buffer = new ArraySegment<byte>(encoded, 0, encoded.Length);
if (cws.State != WebSocketState.Open) return;
Console.WriteLine("Sending speech.config");
await cws.SendAsync(buffer, WebSocketMessageType.Text, true, new CancellationToken());
Console.WriteLine("Sent.");
/* #endregion*/
/* #region Send audio parts. */
var fileInfo = new FileInfo(audioFilePath);
var streamReader = fileInfo.OpenRead();
for (int cursor = 0; cursor < fileInfo.Length; cursor++)
{
outputBuilder.Clear();
outputBuilder.Append("path:audio\r\n");
outputBuilder.Append($"x-requestid:{requestId}\r\n");
outputBuilder.Append($"x-timestamp:{DateTime.UtcNow.ToString("yyyy-MM-ddTHH:mm:ss.fffK")}\r\n");
outputBuilder.Append($"content-type:audio/x-wav");
var headerBytes = Encoding.ASCII.GetBytes(outputBuilder.ToString());
var headerbuffer = new ArraySegment<byte>(headerBytes, 0, headerBytes.Length);
var str = "0x" + (headerBytes.Length).ToString("X");
var headerHeadBytes = BitConverter.GetBytes((UInt16)headerBytes.Length);
var isBigEndian = !BitConverter.IsLittleEndian;
var headerHead = !isBigEndian ? new byte[] { headerHeadBytes[1], headerHeadBytes[0] } : new byte[] { headerHeadBytes[0], headerHeadBytes[1] };
//Audio should be pcm 16kHz, 16bps mono
var byteLen = 8192 - headerBytes.Length - 2;
var fbuff = new byte[byteLen];
streamReader.Read(fbuff, 0, byteLen);
var arr = headerHead.Concat(headerBytes).Concat(fbuff).ToArray();
var arrSeg = new ArraySegment<byte>(arr, 0, arr.Length);
Console.WriteLine($"Sending data from {cursor}");
if (cws.State != WebSocketState.Open) return;
cursor += byteLen;
var end = cursor >= fileInfo.Length;
await cws.SendAsync(arrSeg, WebSocketMessageType.Binary, true, new CancellationToken());
Console.WriteLine("Data sent");
var dt = Encoding.ASCII.GetString(arr);
}
await cws.SendAsync(new ArraySegment<byte>(), WebSocketMessageType.Binary, true, new CancellationToken());
streamReader.Dispose();
/* #endregion*/
{
var startWait = DateTime.UtcNow;
while ((DateTime.UtcNow - startWait).TotalSeconds < 30)
{
await Task.Delay(1);
}
if (cws.State != WebSocketState.Open) return;
}
});
/* #endregion*/
/* #region Wait for tasks to complete */
await Task.WhenAll(sending, receiving);
if (sending.IsFaulted)
{
var err = sending.Exception;
throw err;
}
if (receiving.IsFaulted)
{
var err = receiving.Exception;
throw err;
}
/* #endregion*/
return null;
}
/* #endregion Interface: 'Unscrypt.Bing.SpeechToText.Client.Api.IBingSpeechToTextJobService' Methods */
public class BingSocketAuthentication
{
public static readonly string FetchTokenUri = "https://api.cognitive.microsoft.com/sts/v1.0";
private string subscriptionKey;
private string token;
private Timer accessTokenRenewer;
//Access token expires every 10 minutes. Renew it every 9 minutes.
private const int RefreshTokenDuration = 9;
public BingSocketAuthentication(string subscriptionKey)
{
this.subscriptionKey = subscriptionKey;
this.token = FetchToken(FetchTokenUri, subscriptionKey).Result;
// renew the token on set duration.
accessTokenRenewer = new Timer(new TimerCallback(OnTokenExpiredCallback),
this,
TimeSpan.FromMinutes(RefreshTokenDuration),
TimeSpan.FromMilliseconds(-1));
}
public string GetAccessToken()
{
return this.token;
}
private void RenewAccessToken()
{
this.token = FetchToken(FetchTokenUri, this.subscriptionKey).Result;
Console.WriteLine("Renewed token.");
}
private void OnTokenExpiredCallback(object stateInfo)
{
try
{
RenewAccessToken();
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed renewing access token. Details: {0}", ex.Message));
}
finally
{
try
{
accessTokenRenewer.Change(TimeSpan.FromMinutes(RefreshTokenDuration), TimeSpan.FromMilliseconds(-1));
}
catch (Exception ex)
{
Console.WriteLine(string.Format("Failed to reschedule the timer to renew access token. Details: {0}", ex.Message));
}
}
}
private async Task<string> FetchToken(string fetchUri, string subscriptionKey)
{
using (var client = new HttpClient())
{
client.DefaultRequestHeaders.Add("Ocp-Apim-Subscription-Key", subscriptionKey);
UriBuilder uriBuilder = new UriBuilder(fetchUri);
uriBuilder.Path += "/issueToken";
var result = await client.PostAsync(uriBuilder.Uri.AbsoluteUri, null);
Console.WriteLine("Token Uri: {0}", uriBuilder.Uri.AbsoluteUri);
return await result.Content.ReadAsStringAsync();
}
}
}
}
}
I knew it was going to be simple.
After a frustrating few hours of coding, I've found the problem. I've been forgetting to send a request id along with the speech.config call.

System.Net.Http.HttpClient Timeout seems to be ignored

I am using Xamarin.iOS Version: 8.10.5.26 (Indie Edition) and facing a very strange behaviour with timing out requests sent with HttpClient():
The following code tries to get a result from an url, and has 60 seconds timeout (1 minutes), but when the request is fired it is taking around 90 seconds for time out. When call is made, I manually turn off the network connectivity to check the time out. It is observed that it is taking more than 60 seconds.
CODE
public async Task<Dictionary<string,object>> GetPatientDataASync (string lUsername)
{
var lDict = new Dictionary<string,object> ();
try {
string lQuerystring = "{Email: '" + lUsername + "'}";
String lUrl = String.Format (Constants.mURLPatient + "?where={0}", JObject.Parse (lQuerystring));
var lClient = new HttpClient ();
lClient.BaseAddress = new Uri (lUrl);
lClient.DefaultRequestHeaders
.Accept
.Add (new MediaTypeWithQualityHeaderValue ("application/json"));
lClient.DefaultRequestHeaders.Add ("X-Parse-Application-Id", Constants.mKeyParseAppId);
lClient.DefaultRequestHeaders.Add ("X-Parse-REST-API-Key", Constants.mKeyRestAPIKey);
lClient.Timeout = new TimeSpan (0, 1, 0);
var request = new HttpRequestMessage ();
request.Method = HttpMethod.Get;
if (Utility.isNetworkConnected ()) {
bool responseStatus = false;
await lClient.SendAsync (request)
.ContinueWith (responseTask => {
if (responseTask != null) {
var response = responseTask.Result;
if (response != null) {
if (response.IsSuccessStatusCode) {
var responseContent = response.Content;
if (responseContent != null) {
string responseString = responseContent.ReadAsStringAsync ().Result;
if (!string.IsNullOrWhiteSpace (responseString)) {
JObject json = JObject.Parse (responseString);
if (json != null) {
if (json ["results"].Any ()) {
Patient user = Patient.Instance;
user.objectId = json.SelectToken (#"results[0].objectId").Value<string> ();
user.Email = json.SelectToken (#"results[0].Email").Value<string> ();
user.Name = json.SelectToken (#"results[0].Name").Value<string> ();
user.IsNotificationsEnabled = json.SelectToken (#"results[0].IsNotificationsEnabled").Value<string> ();
Application.Current.Properties ["IsNotificationsEnabled"] = json.SelectToken (#"results[0].IsNotificationsEnabled").Value<string> ();
if (json.SelectToken (#"results[0].DeviceToken") != null) {
var deviceToken = json.SelectToken (#"results[0].DeviceToken").Value<JArray> ();
if (deviceToken != null)
user.DeviceToken = deviceToken.ToObject < List<string>> ();
} else {
user.DeviceToken = new List<string> ();
}
var doctors = json.SelectToken (#"results[0].MyDoctors").Value<JArray> ();
user.AllergicTo = json.SelectToken (#"results[0].AllergicTo").Value<string> ();
user.ContactNo = json.SelectToken (#"results[0].ContactNo").Value<string> ();
user.BloodGroup = json.SelectToken (#"results[0].BloodGroup").Value<string> ();
user.MyDoctors = doctors != null ? doctors.ToObject<List<string>> () : new List<string> ();
responseStatus = true;
} else
responseStatus = false;
}
}
}
}
}
}
});
lDict.Add (SUCCESS_CODE, responseStatus);
return lDict;
} else {
lDict.Add (NO_INTERNET, Constants.mStringNoInternetMessage);
return lDict;
}
} catch (Exception e) {
Debug.WriteLine (e.Message + "\n " + e.StackTrace);
lDict.Add (EXCEPTION_OCCURED, e);
return lDict;
}
}
If there's some mistake in my code , please do let me know.
Also the same issues are reported here :-
First link
Second Link
This is a know bug which was opened, closed and re-opened several times over the years. It is already reported here and here.
There is work-around solution:
Define CancellationTokenSource and Set Token to http request;
Invoke cancel by timeout on CancellationTokenSource like this cts.CancelAfter(timeout);:
Don't forgot catch exception, like this.
try
{
}
catch(TaskCanceledException)
{
if(!cts.Token.IsCancellationRequested)
{// timeout
}
else
{//other reason
}
}

How to stop synchronous request to server in c#?

I know this has been asked before but my case is different. because i have no idea what the following code does. i am just using it as third party open source tool.
I am using open source tool "UnityHTTP" to get response from server.
I would like to get response request cancelled if it is taking a long time.
I am not an expert of C# so i couldn't understand what's going on inside the code the tool provided.
I'd appreciate if someone could help me out here.
the code for getting response is as follows
private void GetResponse() {
System.Diagnostics.Stopwatch curcall = new System.Diagnostics.Stopwatch();
curcall.Start();
try {
var retry = 0;
while (++retry < maximumRetryCount) {
if (useCache) {
string etag = "";
if (etags.TryGetValue (uri.AbsoluteUri, out etag)) {
SetHeader ("If-None-Match", etag);
}
}
SetHeader ("Host", uri.Host);
var client = new TcpClient ();
client.Connect (uri.Host, uri.Port);
using (var stream = client.GetStream ()) {
var ostream = stream as Stream;
if (uri.Scheme.ToLower() == "https") {
ostream = new SslStream (stream, false, new RemoteCertificateValidationCallback (ValidateServerCertificate));
try {
var ssl = ostream as SslStream;
ssl.AuthenticateAsClient (uri.Host);
} catch (Exception e) {
Debug.LogError ("Exception: " + e.Message);
return;
}
}
WriteToStream (ostream);
response = new Response ();
response.request = this;
state = RequestState.Reading;
response.ReadFromStream(ostream);
}
client.Close ();
switch (response.status) {
case 307:
case 302:
case 301:
uri = new Uri (response.GetHeader ("Location"));
continue;
default:
retry = maximumRetryCount;
break;
}
}
if (useCache) {
string etag = response.GetHeader ("etag");
if (etag.Length > 0)
etags[uri.AbsoluteUri] = etag;
}
} catch (Exception e) {
Console.WriteLine ("Unhandled Exception, aborting request.");
Console.WriteLine (e);
exception = e;
response = null;
}
state = RequestState.Done;
isDone = true;
responseTime = curcall.ElapsedMilliseconds;
if ( completedCallback != null )
{
if (synchronous) {
completedCallback(this);
} else {
// we have to use this dispatcher to avoid executing the callback inside this worker thread
ResponseCallbackDispatcher.Singleton.requests.Enqueue( this );
}
}
if ( LogAllRequests )
{
#if !UNITY_EDITOR
System.Console.WriteLine("NET: " + InfoString( VerboseLogging ));
#else
if ( response != null && response.status >= 200 && response.status < 300 )
{
Debug.Log( InfoString( VerboseLogging ) );
}
else if ( response != null && response.status >= 400 )
{
Debug.LogError( InfoString( VerboseLogging ) );
}
else
{
Debug.LogWarning( InfoString( VerboseLogging ) );
}
#endif
}
}
I can see that it something has to do with
the following line lines:-
System.Diagnostics.Stopwatch curcall = new System.Diagnostics.Stopwatch();
curcall.Start();
The request is being made using the TcpClient class. It has a two properties ReceiveTimeout and SendTimeout. After the TcpClient has been initialized, set the a desired value for both of these properties before the connection is made:
var client = new TcpClient ();
client.ReceiveTimeout = 2000; // 2 seconds
client.SendTimeout = 2000; // 2 seconds
Doing this will cause the TcpClient to automatically cancel the request when the timeout has reached.
FYI - The below is only used for measurement of how long the request took.
System.Diagnostics.Stopwatch curcall = new System.Diagnostics.Stopwatch();
curcall.Start();
...
responseTime = curcall.ElapsedMilliseconds;

Sending mails async get executed after view result is returned

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);
}

Categories