让多个线程共享方法工作负载

时间:2018-04-04 23:16:03

标签: c#

我有一个名为WCSession* session = [WCSession defaultSession]; [session updateApplicationContext:applicationDict error:nil]; 的方法,它会发送一个提供它的电子邮件列表,并且我试图弄清楚如何使用多个线程来加速这个过程电子邮件:

asyncStartList

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(); } 是一个简单的SmtpClient和邮件消息。

4 个答案:

答案 0 :(得分:2)

您的解决方案实际上并不是并行运行,因为您等待每个发送操作。您可以使用paralel foreach /作为关键字。否则,您必须等待所有发送操作执行完毕。

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
}

答案 1 :(得分:1)

使用Parallel.ForEach命名空间中的System.Threading.Tasks。相反,for int i = 0;...使用Parallel.ForEach(emailingList, address => {...})

请参阅https://docs.microsoft.com/en-us/dotnet/standard/parallel-programming/how-to-write-a-simple-parallel-foreach-loop以获取示例

答案 2 :(得分:0)

如果您的解决方案的性能是CPU-bound,那就是您想要使用并行线程。如果您的解决方案受到其他内容的约束 - 例如电子邮件服务器处理请求的能力 - 您实际应该使用的是async,这更简单,更安全。

在这种情况下,有很多种方法可以使用异步,但这里有一个简短易用的简单模式:

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

是的,这就是它的全部。这假设您的电子邮件客户端不仅具有MailingFunction()方法,还具有MailingFunctionAsync()方法(例如,使用Outlook&#39; s SendAsync()方法或类似方法)。

以下是从this question中窃取的MailingFunctionAsync()示例:

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);
    }
}

答案 3 :(得分:0)

我只是觉得id很重要。这里常见的答案是使用Parallel.ForEach(除了你应该考虑的 John Wus 答案之外)。虽然一开始Parallel.ForEach似乎是一个简单而好的主意,但它实际上并不是最理想的方法。

这是问题

Parallel.ForEach使用线程池。此外, IO绑定操作将阻止那些等待设备响应并占用资源的线程。

  • 如果您有 CPU绑定代码, Parallelism 是合适的;
  • 虽然如果您有 IO绑定代码, Asynchrony 是合适的。

在这种情况下,发送邮件显然是 I / O ,因此理想的消费代码将是异步的。

此外。

要正确使用 .NET 的异步和并行功能,您还应该了解 I / O线程的概念。

  • 并非程序中的所有内容都会占用CPU时间。当线程尝试从磁盘上的文件读取数据或通过网络发送TCP / IP数据包时,它唯一要做的就是将实际工作委托给设备;磁盘或网络适配器;并等待结果。

  • 花时间等待是非常昂贵的。即使通过线程休眠并且在等待结果时不消耗 CPU 时间,它也不会真正得到回报,因为它浪费了系统资源。

  • 简单来说,每个线程都拥有堆栈变量,本地存储等内存。此外,您拥有的线程越多,在它们之间切换所需的时间就越多。

虽然Parallel.ForEach的优点是易于实现,但您也可以设置Max Degree of Parallelism等选项。

那你能做什么......

您最好对并发任务使用async / await模式和/或某种类型的限制,另一个简洁的解决方案是ActionBlock Class库中的TPL dataflow

数据流示例

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;

这种方法为您提供 Asynchrony ,它还为您提供MaxDegreeOfParallelism,它不会浪费资源,并让 IO 成为 IO 而不会占用不必要的资源

  

免责声明 ,DataFlow可能不是您想要的地方,但我只是认为id会为您提供有关不同内容的更多信息   提供的方法

相关问题