在处理大量电子邮件时,我使用Task
方法处理这些电子邮件asynchronously
,而不会影响主要工作。 (基本上发送电子邮件功能应该在与主线程不同的线程上工作。)想象一下,您在Windows Service
中每30秒处理超过1K的电子邮件。
我面临的问题是 - 很多时候Task
方法没有执行,它完全表现为随机。从技术上讲,它会随机安排任务。有时我会在 SendEmail 方法中收到电话,有时则不会。我尝试了下面提到的两种方法。
方法1
public void ProcessMails()
{
Task.Run(() => SendEmail(emailModel));
}
方法2
public async void ProcessMails()
{
// here the SendEmail method is awaitable, but I have not used 'await' because
// I need non-blocking operation on main thread.
SendEmail(emailModel));
}
有人请让我知道可能是什么问题,或者我在这里遗漏了什么?
答案 0 :(得分:0)
正如已经注意到的那样,似乎您的资源不足以安排最终发送电子邮件的任务。现在,示例代码尝试强制提供所有需要立即调度的工作。
另一个答案提供了使用阻止集合的建议,但我认为有一种更简洁的方法。这个样本至少应该给你正确的想法。
using System;
using System.Threading.Tasks;
using System.Threading.Tasks.Dataflow;
namespace ClassLibrary1
{
public class MailHandler
{
private EmailLibrary emailLibrary = new EmailLibrary();
private ExecutionDataflowBlockOptions options = new ExecutionDataflowBlockOptions() { MaxDegreeOfParallelism = Environment.ProcessorCount };
private ActionBlock<string> messageHandler;
public MailHandler() => messageHandler = new ActionBlock<string>(msg => DelegateSendEmail(msg), options);
public Task ProcessMail(string message) => messageHandler.SendAsync(message);
private Task DelegateSendEmail(string message) => emailLibrary.SendEmail(message);
}
public class EmailLibrary
{
public Task SendEmail(string message) => Task.Delay(1000);
}
}
答案 1 :(得分:-1)
鉴于发送电子邮件的频率相当高,很可能您为调度程序安排了太多Tasks
。
在方法1中,每次调用Task.Run
都会创建一个新任务,每个任务都需要在一个线程上进行调度。通过执行此操作,您很可能正在耗尽线程池。
虽然方法2会减少任务饥饿,即使使用unawaited Task
调用(fire and forget),仍然需要在Threadpool上安排完成async方法之后的继续,这将产生负面影响你的系统。
而不是unawaited Tasks
或Task.Run
,而且由于您是Windows服务,我会改为使用长期运行的后台主题来发送电子邮件。此线程可以独立于您的主要工作,并且可以通过队列将电子邮件安排到此线程。
如果单个邮件发送线程不足以跟上邮件的速度,您可以扩展EmailSender
个线程的数量,但将其约束为合理的有限数量。
您还应该探索其他优化,这将再次提高您的电子邮件发件人的吞吐量,例如
以下是使用BlockingCollection并附上电子邮件消息模型ConcurrentQueue
的示例。
EmailSender
在阻塞收集队列上运行循环,直到调用CompleteAdding
TaskCompletionSource
提供了一个任务,该任务将在所有邮件发送完毕后完成,即可以在不丢失仍在队列中的电子邮件的情况下正常退出。
public class PrimaryWork
{
private readonly BlockingCollection<EmailModel> _enqueuer;
public PrimaryWork(BlockingCollection<EmailModel> enqueuer)
{
_enqueuer = enqueuer;
}
public void DoWork()
{
// ... do your work
for (var i = 0; i < 100; i++)
{
EnqueueEmail(new EmailModel {
To = $"recipient{i}@foo.com",
Message = $"Message {i}" });
}
}
// i.e. Queue work for the email sender
private void EnqueueEmail(EmailModel message)
{
_enqueuer.Add(message);
}
}
public class EmailSender
{
private readonly BlockingCollection<EmailModel> _mailQueue;
private readonly TaskCompletionSource<string> _tcsIsCompleted
= new TaskCompletionSource<string>();
public EmailSender(BlockingCollection<EmailModel> mailQueue)
{
_mailQueue = mailQueue;
}
public void Start()
{
Task.Run(() =>
{
try
{
while (!_mailQueue.IsCompleted)
{
var nextMessage = _mailQueue.Take();
SendEmail(nextMessage).Wait();
}
_tcsIsCompleted.SetResult("ok");
}
catch (Exception)
{
_tcsIsCompleted.SetResult("fail");
}
});
}
public async Task Stop()
{
_mailQueue.CompleteAdding();
await _tcsIsCompleted.Task;
}
private async Task SendEmail(EmailModel message)
{
// IO bound work to the email server goes here ...
}
}
引导和启动上述生产者/消费者类的示例:
public static async Task Main(string[] args)
{
var theQueue = new BlockingCollection<EmailModel>(new ConcurrentQueue<EmailModel>());
var primaryWork = new PrimaryWork(theQueue);
var mailSender = new EmailSender(theQueue);
mailSender.Start();
primaryWork.DoWork();
// Wait for all mails to be sent
await mailSender.Stop();
}
上填写了完整的样本
其他笔记
ConcurrentQueue
)是线程安全的,因此您可以同时使用多个生产者和消费者线程。Task.WaitAll(tasks)
将等待一批任务)。完全异步的发件人显然可以使用await Task.WhenAll(tasks)
。