c# - 从 Parallel.ForEach 循环内部调用异步方法时出现无效操作异常

标签 c# c#-4.0 windows-services

我继承了一个 Windows 服务,该服务可以处理队列中的大量电子邮件。听起来很简单,抓取队列,发送电子邮件,如果 SmtpClient.SendAsync 没有从回调中返回错误,则将数据库中的电子邮件标记为正在发送。我正在使用信号量在线程上等待多个可以调用 SMTP 客户端的异步发送方法。这是我获取状态的唯一方法,并且根据 Microsoft 文档,它必须先完成操作,然后才能使另一个调用异步。现在是有趣的部分。我决定使用 Parallel.ForEach 来让他像这样排队。该方法在Windows服务OnStart中被调用。请注意,我尝试在单独的线程上调用此方法并得到相同的结果。

我在想,要么是,由于我缺乏关于线程的知识,我错过了一些明显的东西,要么是有问题。最有可能的是 A。

 private static void ProcessEmailQueue()
    {
        List<EmailQueue> emailQueue =
            _repository.Select<EmailQueue>().Where(x => x.EmailStatuses.EmailStatus == "Pending").ToList();
        Parallel.ForEach(emailQueue, message =>
                                         {
                                             _smtpMail.FromAddress = message.FromAddress;
                                             _smtpMail.ToAddress = message.ToAddress;
                                             _smtpMail.Subject = message.Subject;
                                             _smtpMail.SendAsHtml = message.IsHtml > 0;
                                             _smtpMail.MessageBody = message.MessageBody;
                                             _smtpMail.UserToken = message.EmailQueueID;
                                             bool sendStatus = _smtpMail.SendMessage();
                                                 // THIS BLOWS UP with InvalidOperation Exception
                                         });
    }

这是从循环内调用的 SMTP 方法。

public bool SendMessage()
    {
        mailSendSemaphore = new Semaphore(0, 10); // This is defined as  private static Semaphore mailSendSemaphore;
        try
        {
            var fromAddress = new MailAddress(FromAddress);
            var toAddress = new MailAddress(ToAddress);

            using (var mailMessage = new MailMessage(fromAddress, toAddress))
            {
                mailMessage.Subject = Subject;
                mailMessage.IsBodyHtml = SendAsHtml;
                mailMessage.Body = MessageBody;
                Envelope = mailMessage;
                smtp.SendCompleted += smtp_SendCompleted;
                smtp.SendAsync(mailMessage, UserToken);
                mailSendSemaphore.WaitOne();
                return _mailSent;
            }
        }
        catch (Exception exception)
        {
            _logger.Error(exception);

            return _mailSent;
        }
    }

SMTP 发送回调

 private void smtp_SendCompleted(object sender, AsyncCompletedEventArgs e)
    {
        if (e.Cancelled)
        {
        }
        if (e.Error != null)
        {
        }
        else
        {
            _mailSent = true;
        }
        mailSendSemaphore.Release(2);
    }

这是异常(exception)情况,由于某些奇怪的原因花了一些时间才得到它。

System.InvalidOperationException was unhandled by user code

Message=异步调用已在进行中。必须先完成或取消它,然后才能调用此方法。 来源=系统 堆栈跟踪: 在 System.Net.Mail.SmtpClient.SendAsync(MailMessage 消息,对象 userToken) 在 SmtpMail.cs 中的 DFW.Infrastruct.Communications.SmtpMail.SendMessage() 处:第 71 行 在 Service1.cs 中的 EmaiProcessorService.EmailQueueService.b_0(EmailQueue 消息):第 57 行 在 System.Threading.Tasks.Parallel.<>c_DisplayClass2d 2.<ForEachWorker>b__23(Int32 i) at System.Threading.Tasks.Parallel.<>c__DisplayClassf 1.b__c() 内部异常:

似乎我的 waitone 被 System.Threading.Tasks.Parallel 删除了

最佳答案

好的,现在我们已经得到了错误文本,看起来相当清楚:

Message=An asynchronous call is already in progress. It must be completed or canceled before you can call this method.

这与 documentation 一致:

两个简单的选项:

  • 创建固定数量的客户端和要发送的消息队列。让每个客户端每次完成时都从队列中获取一条消息,直到队列为空。 BlockingCollection<T> 对此有好处。

  • 创建一个新的 SmtpClient 每条消息。这可能会导致您有效地对 SMTP 服务器发起 DOS 攻击,这并不理想。

说实话,不太清楚你为什么使用 SendAsync当您只是等待消息发送时...

关于c# - 从 Parallel.ForEach 循环内部调用异步方法时出现无效操作异常,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/7434073/

相关文章:

c# - 从通过反射加载的程序集中动态解析类型

c# - 如何在不卸载旧服务的情况下测试新版本的windows服务

c# - Ajax Accordion 、数据集中的特定数据、多个数据集或数据集中的不同数据可能吗?

c# - Signalr Connect 断开并重新连接

c# - 我如何在非 Web 应用程序中使用 Razor...例如邮件合并程序?

asp.net-mvc-3 - 多表单登录页面

c# - 如何创建可以与GUI *或*一起作为C#中的Windows服务运行的Windows应用程序?

C# 从一个配置文件中获取一个字符串以供多个项目使用

c# - 优化 LINQ Count() > X

c# - Entity Framework 模型不显示具有复合键的表