newprint
newprint

Reputation: 7136

How to safely use SmtpClient.SendAsync in Multithreaded application

In my application, I am using ActionBlock from Dataflow library, to send out email alerts using SmtpClient.SendAsync() method, which does not block calling thread.(ActionBlock is getting it's data from BufferBlock, and blocks are tied together using bufferBlock.LinkTo(actionBlock)). However, this method will throw InvalidOperationException if another .SendAsync() call is in progress.

According to MSDN documentation, there is public event SendCompletedEventHandler SendCompleted that is raised when send operation completes.

How do I make sure, that race between threads (or Tasks) spawned by ActionBlock will not cause InvalidOperationException to be thrown ?

One thought, I have so far, is to add to my class(that sends out emails) private lock around SendAsync() call and private function that will be assigned to SendCompleted event. When the thread reaches SendAsync() it obtains the lock, and when the event is raised, private function unlocks the lock, allowing other threads to obtain lock, and progress.

Upvotes: 1

Views: 1096

Answers (2)

i3arnon
i3arnon

Reputation: 116518

You should simply use SendMailAsync instead. It will start with calling SendAsync and complete when SendCompleted is raised.

To make sure only 1 message is sent at each moment you can use SemaphoreSlim set to 1. It's basically an AsyncLock.

However, this will limit you parallelism as you can only perform one operation at a time. You can simply use many different clients. If not one per call then use some resource pool and then you can have concurrency but still keep each client thread-safe.

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

Upvotes: 2

usr
usr

Reputation: 171168

Create one SmtpClient for each send operation. That way there is no need to synchronize anything. Simply enclose it in using to clean up.

Upvotes: 4

Related Questions