I'm using the Twilio C# API to send batches of messages to the Twilio API (typically small amounts, but can grow to several thousand at a time), and in an effort to make the process faster, I started switching everything over to asynchronous calls (as well as some parallel foreaches).
The Twilio API works most of the time when it's completely synchronous, but when I change the request to be asynchronous, I start to get heaps of errors. They all essentially say:
Connection Error: POST
and the internal error is A task was canceled
.
After doing some digging, I've determined that the Twilio API is built on top of the HttpClient
, and it uses the client to send all its requests. However, I can't figure out how to make it all work properly. On average, when sending batches with over 3,000 messages, roughly 20% of them fail.
My sending code is pretty basic
// async SendTextNotificationsAsync
try
{
var result = MessageResource.Create(
to,
from: new PhoneNumber(PhoneNumber),
body: message);
return result;
}
catch (ApiException ex) // API exception, probably an invalid number.
{
LogTwilioBadNumber(ex, phone);
return null;
}
catch (ApiConnectionException ex) // Connection error, let's reattempt
{
if (tryAttempt < 3)
{
System.Threading.Thread.Sleep(500);
// failed call again
return await SendTextNotificationsAsync(phone, message, tryAttempt + 1);
}
if (tryAttempt >= 3)
{
LogTwilioConnectionException(ex, phone, patientId, tryAttempt);
}
return null;
}
The above is called from another method that looks something like:
// async void CompleteCampaignsAsync
await Task.Run(() => Parallel.ForEach(campaign.Details, async n =>
{
await com.SendTextNotificationsAsync(to, message);
}));
And finally, the controller simply calls something like so:
// Don't wait for this to finish, internal processes handle logging
// Will get a time-out error if we wait for this.
await Task.Run(() => cl.CompleteCampaignsAsync()).ConfigureAwait(false);
There are several other steps and layers, so the above is massively simplified. Everything is asynchronous and everything is awaited. The only real exception is the main controller due to it being a long running process that needs to just get kicked off.
In the first method, I've tried both MessageResource.Create
and MessageResource.CreateAsync
. In the latter, I get through about 80 messages before its an unending stream of task cancelled errors. In the former, the process runs to completion, but roughly 20%-50% of the tasks fail.
Is there anything immediately obviously wrong that would allow me to correct this?