C# 并行发送批量电子邮件的最佳方法

C# 并行发送批量电子邮件的最佳方法,c#,.net,parallel-processing,task-parallel-library,.net-4.5,C#,.net,Parallel Processing,Task Parallel Library,.net 4.5,我是TPL(Task Parallel Library)的新手,我很难将流程配置为并行运行任务 我正在开发一个可以发送大量电子邮件的应用程序(比如每分钟数千封),但当我看到处理器的性能时,它并不好:我很确定会有很多开销,因为我没有正确使用任务库 这是我的密码: public async void MainProcess() { var batches = emailsToProcess.Batch(CONST_BATCHES_SIZE); foreach (var ba

我是TPL(Task Parallel Library)的新手,我很难将流程配置为并行运行任务

我正在开发一个可以发送大量电子邮件的应用程序(比如每分钟数千封),但当我看到处理器的性能时,它并不好:我很确定会有很多开销,因为我没有正确使用任务库

这是我的密码:

public async void MainProcess()
{
    var batches = emailsToProcess.Batch(CONST_BATCHES_SIZE);
    
    foreach (var batch in batches.AsParallel()
        .WithDegreeOfParallelism(Environment.ProcessorCount))
    {
         await Task.WhenAll(from emailToProcess in batch 
                    select ProcessSingleEmail(emailToProcess));
        _emailsToProcessRepository.MarkBatchAsProcessed(batch);
    }
}

private async Task ProcessSingleEmail(EmailToProcess emailToProcess)
{
    try
    {
        MailMessage mail = GetMail(emailToProcess); //static light method
        await _smtpClient.SendAsync(sendGridMail);
        emailToProcess.Processed = true;
    }
    catch (Exception e)
    {
        _logger.Error(ErrorHelper.GetExceptionMessage(e, 
                    string.Format("Error sending Email ID #{0} : ", 
                    emailToProcess.Id)), e);
    }
}
(我知道这看起来很糟糕,请随意烤我☺)

我需要它这样做:我需要在一个批处理中处理大量记录(顺便说一句,我使用的是一个允许我使用“批处理”方法的库),因为我需要在流程完成发送时将一批记录标记为已在数据库中处理

该进程实际上正在做我想要的事情:除了速度非常慢之外。正如您在perfmon中看到的,处理器的工作容量不是很高:

最好的方法是什么?有什么建议吗

编辑:我意识到我遇到的是一个开销问题。
是否有任何工具或简单的方法来检测和纠正这些问题?

您所做的不是CPU限制,而是I/O限制,因此,如果处理器可能会影响您的性能,请将并发任务的数量限制为处理器的数量。试着并行启动更多任务

例如,下面的代码将异步处理所有电子邮件,但最多并行处理100封电子邮件。它使用
ForEachAsync
扩展方法进行处理,该方法允许使用参数限制并行度,因此我将尝试将该参数设置得更大

如果可能的话,您可能还希望使
MarkBatchAsProcessed
方法异步,因为这可能会限制性能

public static class Extensions
{
    public static async Task ExecuteInPartition<T>(IEnumerator<T> partition, Func<T, Task> body)
    {
        using (partition)
            while (partition.MoveNext())
                await body(partition.Current);
    }

    public static Task ForEachAsync<T>(this IEnumerable<T> source, int dop, Func<T, Task> body)
    {
        return Task.WhenAll(
            from partition in Partitioner.Create(source).GetPartitions(dop)
            select ExecuteInPartition(partition, body));
    }
}

public Task MainProcess()
{
    // Process 100 emails at a time
    return emailsToProcess.ForEachAsync(100, async (m) =>
    {
        await ProcessSingleEmail(m);                
    });

    _emailsToProcessRepository.MarkBatchAsProcessed(emailsToProcess);
}

我认为限制因素是您的网络带宽,而不是您的CPU…尽可能地并行化您的CPU,这不会给您更快的网络连接。@abelenky感谢您的回答,但我认为这不是问题所在。我甚至尝试用Task.Delay(1500)替换SendAsync,这大约是发送电子邮件所需的时间,结果完全相同。当然,您的网络是这里的限制因素-但我认为您可以做的不仅仅是
。在这里使用degreeofparallelism(Environment.ProcessorCount)
,因为您正在尝试异步(进行一点实验)-除此之外,你可能必须尝试替代框架Smtpclient…顺便问一下:这将是一种什么样的批量电子邮件?我们有足够多的垃圾邮件;)哈哈,谢谢@CarstenKönig,这不会是垃圾邮件,只是一个像网站这样的小“linkedin”提醒。我不认为网络是问题所在,因为我尝试用一个任务模拟smtpclient。延迟意味着没有调用其他需要网络的操作,结果是相同的。很好的观察,我使用的不是SmtpClient,而是使用http的Sendgrid api。不,你说你用一个任务模拟了它。延迟1.5秒,得到了相同的结果(这证明了什么)-所以如果你的发送确实需要1.5秒。(你无法控制的代码位)显然有地方可以开始寻找(你可以像我说的那样增加并行任务的数量-但1500毫秒是巨大的!)感谢@ned stoyanov花时间向我展示这种方法!!我想100是指分区的数量,而不是分区的大小。2) 我应该在哪里更新批次?我应该将其作为操作参数传递给同一个扩展方法,并在使用(分区)结束时执行吗?
public async Task MainProcess()
{
    var batches = emailsToProcess.Batch(CONST_BATCHES_SIZE);

    foreach (var batch in batches)
    {
         return batch.ForEachAsync(batch.Count, async (m) =>
         {
             await ProcessSingleEmail(m);                
         });

       _emailsToProcessRepository.MarkBatchAsProcessed(batch);             
    }
}