0

I have a method called asyncStartList, which sends a list of emails provided it, and I'm trying to figure out how to use multiple threads to speed up the process in cases where there are a lot of emails:

public async Task asyncStartList()
{
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();   

    for (int i = 0; i < listLength; i++)
    {
        currentMailAddress = emailingList[i];
        await Task.Run(() => MailingFunction());
        currentMailAddress = "";
        Console.WriteLine("Your mail to {0} was successfully sent!", emailingList[i]);
    }

    stopWatch.Stop();
    TimeSpan ts = stopWatch.Elapsed;

    string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", 
        ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);

    Console.WriteLine("Time for completion " + elapsedTime);
    Console.ReadLine();
}

The MailingFunction() is a simple SmtpClient and mail message.

Rufus L
  • 36,127
  • 5
  • 30
  • 43
Kop Lop
  • 29
  • 6

4 Answers4

2

Your solution actually not run parallel, because of you wait for every each send operation. You can use paralel foreach/for keyword. Otherwise, you have to wait after all send operation executed.

public async Task asyncStartList()
{
    Stopwatch stopWatch = new Stopwatch();
    stopWatch.Start();

    // option 1
    Task[] tasks = emailingList.Select(s => Task.Run(() => { SendEmail(s); }).ToArray();

    Task.WaitAll(tasks);
    // option 1 end

    // option 2
    Parallel.ForEach(emailingList, email =>
    {
        SendEmail(email);
    });
    // option 2 end

    // option 3
    Parallel.For(0, emailingList.Length, i =>
    {
        SendEmail(emailingList[i]);
    });
    // option 3 end

    stopWatch.Stop();
    TimeSpan ts = stopWatch.Elapsed;

    string elapsedTime = String.Format("{0:00}:{1:00}:{2:00}.{3:00}", ts.Hours, ts.Minutes, ts.Seconds, ts.Milliseconds / 10);

    Console.WriteLine("Time for completion " + elapsedTime);
    Console.ReadLine();
}

private void SendEmail(string emailAddress)
{
    // Do send operation
}
Adem Catamak
  • 1,987
  • 2
  • 17
  • 25
1

Use Parallel.ForEach from the System.Threading.Tasks namespace. So instead for for int i = 0;... use Parallel.ForEach(emailingList, address => {...})

See https://learn.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-write-a-simple-parallel-foreach-loop for an example

see sharper
  • 11,505
  • 8
  • 46
  • 65
  • This is the way to go. I have used Parallel before to enable data parallelism. The execution is scheduled on multiple threads based on your system environment. – Junius Apr 04 '18 at 23:30
0

If your solution's performance is CPU-bound, that is when you want to use parallel threads. If your solution is bound by something else-- e.g. the ability of the email server to handle requests-- what you actually should use is async, which is much simpler and much safer.

There are many ways to use async in this scenario, but here is a short and simple pattern that would work:

await Task.WhenAll
(
    emailingList.Select( async address => MailingFunctionAsync(address) )
);

Yes, that is all there is to it. This assumes that your email client not only has a MailingFunction() method but also a MailingFunctionAsync() method (e.g. using Outlook's SendAsync() method or something similar).

Here is a sample MailingFunctionAsync() stolen from this question:

public async Task MailingFunctionAsync(string toEmailAddress)
{
    var message = new MailMessage();
    message.To.Add(toEmailAddress);
    message.Subject = SOME_SUBJECT;
    message.Body = SOME_BODY;
    using (var smtpClient = new SmtpClient())
    {
        await smtpClient.SendMailAsync(message);
    }
}
John Wu
  • 50,556
  • 8
  • 44
  • 80
0

The common answer here is to use Parallel.ForEach (well apart from John Wu's answer that you should really consider). While on-the-outset Parallel.ForEach seems like an easy and good idea, its actually not the most optimal approach.

Here is the problem:

Parallel.ForEach uses the thread pool. Moreover, IO bound operations will block those threads waiting for a device to respond and tie up resources.

  • If you have CPU bound code, Parallelism is appropriate;
  • Though if you have IO bound code, Asynchrony is appropriate.

In this case, sending mail is clearly I/O, so the ideal consuming code would be asynchronous.

Furthermore, to use asynchronous and parallel features of the .NET properly, you should also understand the concept of I/O threads.

  • Not everything in a program consumes CPU time. When a thread tries to read data from a file on disk or sends a TCP/IP packet through network, the only thing it does is delegate the actual work to a device; disk or network adapter; and wait for results.

  • It’s very expensive to spend a threads time on waiting. Even through threads sleep and don’t consume CPU time while waiting for the results, it doesn’t really pay off because it’s a waste of system resources.

  • To be simplistic, every thread holds memory for stack variables, local storage and so on. Also, the more threads you have, the more time it takes to switch among them.

Though, the nice thing about Parallel.ForEach is its easy to implement, you can also set up options like Max Degree of Parallelism.

So what can you do...

You are best to use async/await pattern and/or some type of limit on concurrent tasks, another neat solution is to ActionBlock<TInput> Class in the TPL dataflow library.

Dataflow example

var block = new ActionBlock<MySomething>(
    mySomething => MyMethodAsync(mySomething),
    new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 50 });

foreach (var something in ListOfSomethings)
{
    block.Post(something );
}

block.Complete();
await block.Completion;

This approach gives you Asynchrony, it also gives you MaxDegreeOfParallelism, it doesn't waste resources, and lets IO be IO without chewing up unnecessary resources

Disclaimer, DataFlow may not be where you want to be, however I just thought I'd give you some more information on the different approaches on offer.

halfer
  • 19,824
  • 17
  • 99
  • 186
TheGeneral
  • 79,002
  • 9
  • 103
  • 141