Estrangulamento velocidade do processo de envio de e-mail
-
19-08-2019 - |
Pergunta
Desculpe o título é um pouco ruim, eu não conseguia palavra-lo corretamente.
Edit: Devo observar este é um aplicativo de console c #
Eu tenho um protótipo um sistema que funciona como assim (este é áspera pseudo-codeish):
var collection = grabfromdb();
foreach (item in collection) {
SendAnEmail();
}
SendAnEmail:
SmtpClient mailClient = new SmtpClient;
mailClient.SendCompleted += new SendCompletedEventHandler(SendComplete);
mailClient.SendAsync('the mail message');
SendComplete:
if (anyErrors) {
errorHandling()
}
else {
HitDBAndMarkAsSendOK();
}
Obviamente, esta configuração não é ideal. Se a coleção inicial tem, digamos, 10.000 registros, em seguida, ele vai nova-se 10.000 casos de SmtpClient de forma bastante curto tão rapidamente como ele pode percorrer as linhas -. E provável Asplode no processo
Meu jogo final ideal é ter algo como 10 email concorrente sair ao mesmo tempo.
Uma solução hacky vem à mente: Adicionar um contador, que incrementos quando SendAnEmail () é chamado, e diminui quando o SendComplete é enviado. Antes SendAnEmail () é chamado no circuito inicial, verificar o contador, se for muito alto, então o sono por um pequeno período de tempo e, em seguida, verificá-lo novamente.
Eu não tenho certeza que é uma grande idéia, e figura a mente SO colméia teria uma maneira de fazer isso corretamente.
Eu tenho muito pouco conhecimento de threading e não tenho certeza se seria um uso apropriado aqui. Por exemplo, o envio de e-mail em uma discussão de fundo, verifique primeiro o número de threads de criança para garantir que não é demais ser usado. Ou se houver algum tipo de 'estrangulamento tópico' construído em.
Atualização
Seguindo o conselho de Steven A. Lowe, agora tenho:
- Um dicionário segurando meus e-mails e uma chave única (este é o e-mail that
- Um Método FillQue, que preenche o dicionário
- Método A ProcessQue, que é uma discussão de fundo. Ele verifica o that, e SendAsycs qualquer e-mail em que.
- Um delegado SendCompleted que remove o e-mail do que. E chama FillQue novamente.
Eu tenho alguns problemas com esta configuração. Eu acho que eu perdi o barco com a discussão de fundo, eu deveria estar gerando um destes para cada item no dicionário? Como posso obter a linha para 'hang around' por falta de uma palavra melhor, se esvazia Email que as extremidades da linha.
atualização final
Eu coloquei um 'while (true) {}' na discussão de fundo. Se o that está vazio, ele espera alguns segundos e tenta novamente. Se o that é repetidamente esvaziar, i 'break' ao mesmo tempo, e as extremidades programa ... funciona bem. Estou um pouco preocupado com o negócio 'while (true)' embora ..
Solução
Resposta Curta ??h1>
O uso de uma fila como um tampão finito, processado por sua própria rosca.
Long Resposta ??h1>
Chamar um método de preenchimento-fila para criar uma fila de e-mails, limitado a (digamos) 10. preenchê-lo com os primeiros 10 e-mails não enviados. Lançar um thread para processar a fila - para cada e-mail na fila, enviá-lo asynch. Quando a fila está vazia sono por um tempo e de verificação de novo. Já o delegado conclusão remover o enviado ou e-mail errored da fila e atualizar o banco de dados, em seguida, chamar o método de preenchimento-fila para ler e-mails mais não enviadas para a fila (back até o limite).
Você só vai precisar de bloqueios em torno das operações de fila, e só terá de gerir (diretamente) a um segmento para processar a fila. Você nunca vai ter mais de N + 1 tópicos ativos ao mesmo tempo, onde N é o limite da fila.
Outras dicas
Eu acredito que a sua solução hacky realmente iria funcionar. Apenas certifique-se você tem uma declaração de bloqueio em torno dos bits onde você aumentar e diminuir o contador:
class EmailSender
{
object SimultaneousEmailsLock;
int SimultaneousEmails;
public string[] Recipients;
void SendAll()
{
foreach(string Recipient in Recipients)
{
while (SimultaneousEmails>10) Thread.Sleep(10);
SendAnEmail(Recipient);
}
}
void SendAnEmail(string Recipient)
{
lock(SimultaneousEmailsLock)
{
SimultaneousEmails++;
}
... send it ...
}
void FinishedEmailCallback()
{
lock(SimultaneousEmailsLock)
{
SimultaneousEmails--;
}
... etc ...
}
}
Gostaria de acrescentar todas as minhas mensagens para uma fila, e em seguida, desova ou seja, 10 tópicos que enviou e-mails até que a fila estava vazia. Pseudo'ish C # (provavelmente não compile):
class EmailSender
{
Queue<Message> messages;
List<Thread> threads;
public Send(IEnumerable<Message> messages, int threads)
{
this.messages = new Queue<Message>(messages);
this.threads = new List<Thread>();
while(threads-- > 0)
threads.Add(new Thread(SendMessages));
threads.ForEach(t => t.Start());
while(threads.Any(t => t.IsAlive))
Thread.Sleep(50);
}
private SendMessages()
{
while(true)
{
Message m;
lock(messages)
{
try
{
m = messages.Dequeue();
}
catch(InvalidOperationException)
{
// No more messages
return;
}
}
// Send message in some way. Not in an async way,
// since we are already kind of async.
Thread.Sleep(); // Perhaps take a quick rest
}
}
}
Se a mensagem é a mesma, e apenas ter muitos destinatários, apenas trocar a mensagem com um destinatário, e adicionar um único parâmetro Mensagem para o método de envio.
Você pode usar um temporizador .NET para configurar a programação para o envio de mensagens. Sempre que os fogos temporizador, pegar os próximos 10 mensagens e enviá-los todos, e repita. Ou se você quiser uma taxa geral (10 mensagens por segundo) você poderia ter o fogo temporizador a cada 100ms, e enviar uma única mensagem de cada vez.
Se precisar de programação mais avançado, você pode olhar para um quadro de programação como Quartz.NET
Não é este algo que Thread.Sleep()
pode segurar?
Você está correto em pensar que enfiar fundo pode servir um bom propósito aqui. Basicamente o que você quer fazer é criar uma discussão de fundo para este processo, deixá-lo correr o seu próprio caminho, atrasos e tudo, e, em seguida, encerrar o segmento quando o processo é feito, ou deixá-lo indefinidamente (transformando-o em um serviço do Windows ou algo similar será uma boa idéia).
Um pouco introdução no pode ser lido aqui (com Thread.Sleep incluído multi-threading! ) .