タイトルのとおりなのですが、C#でParallel(ForEach)を用いて並列でメール送信するとSMTP接続で一部がタイムアウトになる現象に遭遇しました。

現象と環境


環境とか現象とかです。

  • .NET Framework 4.8
  • 100件くらいの送信
  • 30~40件くらいタイムアウトになる。残りは正常に送信できる。
  • 送信はAWS SESを使用
  • AWS SES のエンドポイントにはtelnetで接続できる
    • そもそも失敗しているのは30~40%なのでAWS SES側の問題でないように思える
  • 見た感じフン詰まってタイムアウトしているようにみえる

原因


.NET FrameworkではデフォルトでTCPコネクション数が制限されているようです。これの設定を変えればタイムアウトが発生することなくParallel.ForEachを用いて全てのメールを送信することができました。

connectionManagement 要素 (ネットワーク設定)

何スレッドで動かすか?


さて、まず何スレッドで動かすかを決めます。送信するマシンのスペックは下記のとおりです。

  • i7-7700k 4コア 8スレッド

合計32スレッドなので半分の16スレッドくらいで動かしてよいだろうと判断しました。

設定を変える


最大コネクション数はApp.Configに下記の<system.net>の部分を追記することで変更できます。とりあえず最大スレッド数の16に合わせる形で16としました。

1
2
3
4
5
6
7
8
<?xml version="1.0" encoding="utf-8"?>
<configuration>
<system.net>
<connectionManagement>
<add address = "*" maxconnection = "16" />
</connectionManagement>
</system.net>
</configuration>

コード


次にParallel.ForEachを用いてメール送信するコードのサンプルです。今回は16スレッドで動かすのでParallelOptionsインスタンスを生成し、並列処理数を指定、Parallel.ForEachの第2引数に渡します。

List<data>に送信するメールの情報(宛先・題名など)が入っていると思ってください。その他、C#でのSMTPメール送信についてはだいぶ以前に書いたのでそちらでも参考にしてください。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
ParallelOptions parallelOptions = new ParallelOptions();
parallelOptions.MaxDegreeOfParallelism = 16;

Parallel.ForEach(List<data>, parallelOptions, data =>
{
using (var smtpClient = new SmtpClient(host, port))
{
smtpClient.Timeout = 60000;
smtpClient.Credentials = new NetworkCredential(user, password);
smtpClient.EnableSsl = true;

using (var mail = new System.Net.Mail.MailMessage())
{
mail.From = new System.Net.Mail.MailAddress("sender@example.com");
mail.To.Add(data.Email);
mail.Subject = data.Subject;
mail.SubjectEncoding = Encoding.UTF8;
mail.Body = data.Body;
mail.BodyEncoding = Encoding.UTF8;

smtpClient.Send(mail);
}
}
});

送信時間など


いちおう、3パターンで試してみました。本来であれば複数回で計測するべきでしょうが めんどくさかったので やってません。4 → 16 では差はありましたが、16 → 24ではほとんど差はありませんでした。この結果からとりあえず16で動かすことにしました。

スレッド数最大コネクション数時間
441分40秒
161659秒
242456秒

繰り返しますがそれぞれ一回しか計測していないので、16→24ではもう少し差が出る可能性はあります。また、32スレッドだと100%使い切ってしまう(4コア × 8スレッド)ため試してません。

所感


いちおう送信できるようになりました。ただ、.NET Frameworkのデフォルトの最大コネクション数がいくつなのかはわからずじまいです。