Я пытаюсь отправить любое количество писем в кратчайшие сроки с помощью .NET 5.0?
Я играл с чем-то вроде следующего, но я не уверен, оптимально это или даже правильно, поскольку есть ряд элементов, которые я не понимаю.
public async Task SendEmailAsync(string subject, string htmlMessage,
IEnumerable<string> recipients, string? attachment)
{
using SemaphoreSlim semaphore = new(10, 10);
await Task.WhenAll(recipients.Select(async recipient =>
{
await semaphore.WaitAsync();
try
{
return SendEmailAsync(subject, htmlMessage, recipient, attachment);
}
finally
{
semaphore.Release();
}
}));
}
Может ли кто-нибудь уточнить, правильно ли это, или сообщить мне, знают ли они лучший подход?
2 ответа
Нет, этот код не уважает семафор. Он просто отправляет все электронные письма без ограничения степени параллелизма семафором. Так как return SendEmailAsync
возвращается сразу после Task
объект получен, а метод не завершен, семафор немедленно освобождается. Таким образом, семафор воспроизводит только запросы на создание, что, как я полагаю, будет быстрым.
Исправление await
в try
пункт.
public async Task SendEmailAsync(string subject, string htmlMessage,
IEnumerable<string> recipients, string? attachment)
{
using SemaphoreSlim semaphore = new(10);
await Task.WhenAll(recipients.Select(recipient =>
SendEmailAsync(subject, htmlMessage, recipient, attachment, semaphore)));
}
private async Task SendEmailAsync(string subject, string htmlMessage,
string recipient, string? attachment, SemaphoreSlim semaphore)
{
await semaphore.WaitAsync();
try
{
await SendEmailAsync(subject, htmlMessage, recipient, attachment);
}
finally
{
semaphore.Release();
}
}
Остальная часть кода мне подходит. Вероятно, я могу только предложить использовать ООП для инкапсуляции данных для массовой рассылки по электронной почте в какой-то класс. Это упростило бы улучшение кода пушнины.
Ваш подход может привести к огромному количеству активных задач в любой момент времени, каждая из которых представляет собой нагрузку на ресурсы ввода-вывода машины. Вам нужен способ ограничить количество задач.
Ваш подход также довольно плохо злоупотребляет вызовом Select (). По крайней мере, это делает код очень трудным для чтения.
Вот демонстрация с использованием фиксированного количества задач для имитации отправки электронного письма 1000 получателям. Обратите внимание, что доступ к общей Queue <> не нужно синхронизировать в этом примере, но это может потребоваться в зависимости от вызова API, используемого на практике, поэтому я добавил синхронизацию. Достаточно простой блокировки {}.
private static readonly Random random = new Random();
private static readonly Queue<string> recipients = new Queue<string>();
protected override async Task Run()
{
for (int i = 1; i <= 1000; ++i)
{
recipients.Enqueue($"recipient_{i:00000}@emaildomain.com");
}
List<Task> tasks = new List<Task>();
for (int i = 1; i <= 50; ++i)
{
tasks.Add(SendEmails($"Task {i:00000}"));
}
await Task.WhenAll(tasks);
}
private static async Task SendEmails(string taskName)
{
for (; ;)
{
string recipient;
lock (recipients)
{
if (recipients.Count == 0)
{
break;
}
recipient = recipients.Dequeue();
}
Debug.WriteLine($"{taskName}: Sending to {recipient}...");
await SendEmailAsync(recipient);
Debug.WriteLine($"{taskName}: Sending to {recipient} complete");
}
Debug.WriteLine($"{taskName}: No more recipients; quitting");
}
private static async Task SendEmailAsync(string recipient)
{
// Simulate sending an email with random network latency.
await Task.Delay(random.Next(100, 2000));
}
Queue<string>
не является потокобезопасным и может быть поврежден, если асинхронные вызовы выполняются не на однопоточномSynchronizationContext
. все происходит в одном потоке Я не уверен, правда ли это.— эспот
@aepot хороший момент. В этом примере все происходит в одном потоке, но вы правы, что продолжения могут быть запланированы для разных потоков в зависимости от того, как был реализован API. Отредактирую, чтобы добавить блокировку.
— гленебоб
ConcurrentQueue
может быть быстрее, чемlock
. Также вы можете обновить текст.— эспот
- 1
@aepot это, наверное, не имеет значения. Очередь предназначена только для поддержки примера параллелизма задач, который я собрал. OP не может пойти по этому пути. Важная часть — как ограничить количество задач pf без использования SemaphoreSlim.
— гленебоб
- 1
Хорошо, но я не уверен, что отказ от семафора сделает код более эффективным. В любом случае решение мне нравится. Сделаем несколько тестов, чтобы убедиться.
— эспот
Разве это не то же самое, что добавить
await
ключевое слово перед звонкомSendEmailAsync()
в моем коде?— Джонатан Вуд
@JonathanWood, наверное, но ты не можешь вернуться
void
вSelect
, верно? Я не силен в синтаксисе лямбда-выражений без IDE, просто написал ответ с мобильного. В любом случае, методоподобный подход по производительности не уступает лямбда-выражению.— эспот
@JonathanWood попробуйте поменять
return
кawait
. Если компиляция прошла успешно, то все готово.— эспот
Да, похоже, это работает. Лямбда не обязательно ничего возвращает.
— Джонатан Вуд