2016-03-28 2 views
1

Проект MVC поставляется с частично встроенными службами электронной почты. Я реализовал класс EmailService, который работает нормально. Тем не менее, мне нужно отправить электронные письма навалом для нескольких пользователей, поэтому я хотел бы запустить этот процесс в фоновом режиме, и именно здесь я застрял. Я пробовал следующие коды:SendEmailAsync в фоновом режиме

//To simplify the example, I will just use the loop 
for (int i = 1; i <= 10; i++) 
{ 
    //Version 1: 
    await UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body"); 

    //Version 2: 
    UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body"); 

    //Version 3: 
    ThreadPool.QueueUserWorkItem(o => UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body")); 

    //Version 4: 
    Task.Run(async() => { await UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body"); }); 

    //Version 5: 
    await Task.Run(() => { UserManager.SendEmailAsync(userId + i, "Test subject line", "Sample email body"); }); 
} 

Версия 1 является единственным рабочим кодом. Но await заблокирует код, который вызывает длительную задержку, прежде чем перейти к следующей странице.

Версия 2, 3 и 4 не работают вообще.

Версия 5 имеет очень странное поведение. Я всегда получаю меньше электронных писем. Используя i, чтобы отслеживать, какая электронная почта мне не хватает, она всегда является последней, которая отсутствует. (в приведенном выше примере я всегда получаю электронную почту только от userId от 1 до 9.) И если я попытаюсь отправить только одно электронное письмо, то я никогда не смогу его получить.

Что пошло не так?

Отредактировано Спасибо всем за ответ, но я, вероятно, следует отметить, что:

  1. В то время я хочу «огонь и забыть», я почти уверен, что у меня есть все, что нужно и любой процесс, после чего не зависит от этого процесса.

  2. Я не забочусь успех/провал этого процесса. (Возможно будет регистрировать ошибку, но не все равно много, если это не удается)

+0

Я не совсем уверен, что вы подразумеваете под первой версией, блокирует ваш код, но как просто удалить «ожидание»? Насколько я понял, вы хотите как можно быстрее отправить содержимое страницы пользователю, не заставляя его ждать, пока все электронные письма не будут отправлены? –

+0

Вы закрываете переменную цикла. Есть несколько сообщений, объясняющих это. Вот [один] (http://stackoverflow.com/questions/451779/how-to-tell-a-lambda-function-to-capture-a-copy-instead-of-a-reference-in-c) , Цикл Foreach изменяется в C# 5.0, но остается неизменным. Вам нужно взять копию переменной 'i', чтобы исправить ее. –

+0

Возможный дубликат [Есть ли причина повторного использования C# переменной в foreach?] (Http://stackoverflow.com/questions/8898925/is-there-a-reason-for-cs-reuse-of-the -variable-in-a-foreach) –

ответ

2

Одним из решений этой проблемы является асинхронное отключение всех ваших асинхронных запросов электронной почты и помещение этих задач в коллекцию, а затем ожидание набора задач. Вероятно, это будет немного быстрее, поскольку каждому запросу не придется ждать запроса по электронной почте до его завершения, вместо этого они могут произойти одновременно.

Пример:

var myTasks = new List<Task>(); 
myTasks.Add(UserManager.SendEmailAsync(userId, "Test subject line", "Sample email body")); 
myTasks.Add(UserManager.SendEmailAsync(userId, "Test subject line", "Sample email body")); 
myTasks.Add(UserManager.SendEmailAsync(userId, "Test subject line", "Sample email body")); 
await Task.WhenAll(myTasks); 

Если вы хотите полномасштабный огнь и забыть, что, вероятно, будет не рискованными независимо от того, как вы его фрагмента, проверить этот пост от Stephen Cleary на использование QueueBackgroundWorkItem. Я бы постарался поставить все задачи в коллекцию, хотя и ожидал их, чтобы судить о производительности до перехода на ядерный вариант.

+0

Моя озабоченность этим подходом заключается в том, что иногда мне приходится отправлять до 5000 писем (на каждый объем). Мой первоначальный план состоял в том, чтобы пойти с полным взрывом «огонь и забыть», но внутри метода «огонь и забыть» у меня будет «попытка» ... «поймать» и зарегистрировать любую ошибку. –

+0

Другим словом, мне было бы все равно, даже если есть какая-либо ошибка. Мы хотим, чтобы процесс двигался, даже если произошла ошибка. –

+0

@ C.J. Тогда я буду рассматривать QueueBackgroundWorkItem, что объясняется в связанной статье. Вы пытались выполнить этот метод, хотя с ожиданием Task.WhenAll() знать, что у вас есть узкое место в производительности? Есть ли у вас другие возможности, такие как распределенная система? Я бы действительно не хотел запускать и забывать электронные письма 5K, когда у вас могут появиться еще 5K запросов, которые все должны запускать и забывать о 5K-письмах. – davidallyoung

3

... запустить процесс в фоновом режиме, и это где я застрял

Вы не должны выполнять длинные запросы погонных, которые имеют решающее значение с помощью веб-запроса. Причина номер один - вы должны планировать неудачу. Что произойдет, если пул приложений будет переработан? В этом случае работа может быть потеряна, и вы могли бы заметить, что необходимо, чтобы состояние было необходимым для выполнения выполнения, когда оно было остановлено (также как вы могли бы выбрать его после отказа)? Запрос также мог бы отключить клиентскую сторону, заставив пользователя поверить (неправильно), что произошел сбой. Вы также можете пережевывать ресурсы веб-сайта (процессор, сеть и т. Д.), Когда вы не можете себе это позволить.

Существуют различные рамки, которые могут помочь вам в этом. Проверьте это (IMO) очень хорошая статья Скотта Хансельмана под названием How to run Background Tasks in ASP.NET.Он повторяет различные существующие рамки, которые позволят вам сделать это и даже запланировать работу, которая будет выполнена в будущем, если вы пожелаете.

Моя рекомендация заключается в том, чтобы всегда выгружать процессы, подобные этому, в службу Windows (или другую службу), а не выполнять их в веб-запросе (под этим я имею в виду пул процессов/приложений веб-сайта).


Редактироватьна основе последнего редактирования

Я знаю его не то, что вы хотите услышать (извините), но я не думаю, что это изменит этот ответ или другие данные ответы. ASP.NET (, включая MVC, который находится поверх asp.net) был просто не разработан с целью выполнения длительных запросов (как создание и отправка 5000 писем). Вы также указываете на ведение журнала, если он терпит неудачу, при таком длинном запросе, как это, нет гарантии, что произойдет ведение журнала. Перезапуск пула приложений эквивалентен Process.Kill(), даже если у вас есть код очистки и код регистрации, нет гарантии, что он будет выполнен. Возможно, вы никогда не узнаете, был ли провал.

Смежные вопросы