Как безопасно использовать SmtpClient.SendAsync в многопоточном приложении

В своем приложении я использую ActionBlock из библиотеки Dataflow для отправки предупреждений по электронной почте с помощью SmtpClient.SendAsync(), который не блокирует вызывающий поток (ActionBlock получает данные от BufferBlock, а блоки связываются вместе с помощью bufferBlock.LinkTo(actionBlock)). Однако этот метод выдаст InvalidOperationException, если выполняется другой вызов .SendAsync().

Согласно MSDN документации, есть public event SendCompletedEventHandler SendCompleted, который возникает, когда операция отправки завершается.

Как мне убедиться, что гонка между потоками (или Tasks), порожденными ActionBlock, не приведет к броску InvalidOperationException?

Одна мысль, которая у меня есть до сих пор, состоит в том, чтобы добавить в мой класс (который отправляет электронные письма) приватную блокировку вокруг вызова SendAsync() и приватную функцию, которая будет назначена событию SendCompleted. Когда поток достигает SendAsync(), он получает блокировку, а когда возникает событие, приватная функция снимает блокировку, позволяя другим потокам получить блокировку и продолжить работу.


person newprint    schedule 20.02.2015    source источник


Ответы (2)


Создайте один SmtpClient для каждой операции отправки. Таким образом, нет необходимости ничего синхронизировать. Просто заключите его в using для очистки.

person usr    schedule 20.02.2015

Вместо этого вы должны просто использовать SendMailAsync. Он начнется с вызова SendAsync и завершится, когда будет поднят SendCompleted.

Чтобы убедиться, что в каждый момент отправляется только одно сообщение, вы можете использовать SemaphoreSlim, установленный на 1. По сути, это AsyncLock.

Однако это ограничит параллелизм, так как вы можете выполнять только одну операцию за раз. Вы можете просто использовать много разных клиентов. Если не по одному на вызов, используйте некоторый пул ресурсов, и тогда вы можете иметь параллелизм, но при этом поддерживать потокобезопасность каждого клиента.

var client = _smtpClientPool.Get();
try
{
    await client.SendMailAsync(...)
}
finally
{
    _smtpClientPool.Put(client);
}
person i3arnon    schedule 20.02.2015