2011-09-15 18 views
2

キュー内の多数の電子メールを処理するWindowsサービスを継承しました。単純に聞こえる、キューをつかむ、SmtpClient.SendAsyncがコールバックからエラーを返さない場合、送信されたとしてDBの電子メールにフラグを立てる..私はセマフォを使用しているため、 SMTPクライアントのAsync Sendメソッドを呼び出すことができます。これはステータスを取得する唯一の方法であり、別の呼び出しを非同期にする前に、Microsoftのドキュメントごとに操作を終了する必要があります。今は楽しい部分のために。私はParallel.ForEachを使用して、彼のようにキューを取得することにしました。このメソッドは、WindowsサービスのOnStartで呼び出されます。私は別のスレッドでこのメソッドを呼び出そうとしましたが、同じ結果を得ることに注意してください。Parallel.ForEachループ内から非同期メソッドを呼び出す際の操作の例外が無効です

私は、スレッドの知識が不足しているために、何かが分かりにくいと思っています。可能性が高い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のCALLBACKここ

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

を送る例外で、いくつかの奇妙な理由でそれを得るために、いくつかを取りました。

System.InvalidOperationException was unhandled by user code 

メッセージ=非同期呼び出しはすでに進行中です。このメソッドを呼び出すには、完了またはキャンセルする必要があります。 は、Source =システム のStackTrace:System.Net.Mail.SmtpClient.SendAsyncで (はMailMessageメッセージはUserTokenオブジェクト) DFW.Infrastructure.Communications.SmtpMail.SendMessageで()SmtpMail.cs中:EmaiProcessorService.EmailQueueServiceでライン71 .B_ 0(EmailQueueメッセージ)のService1.cs:行57 at System.Threading.Tasks.Parallel。 <は> C _DisplayClass2d 2.<ForEachWorker>b__23(Int32 i) at System.Threading.Tasks.Parallel.<>c__DisplayClassf 1.b__c() のInnerException:

は今、私たちはエラーテキストを持っていること、それはそう、私のWAITONEさてSystem.Threading.Tasks.Parallel

+0

InvalidOperationExceptionの*詳細*を渡してください。どのラインが例外を投げているのですか?メッセージは何ですか? –

+0

私たちが話しているようにキャッチしようとすると、私はtry catchブロックのどれかでそれを得ることができないので、幾分埋め込まれているようだ。 – CrazyCoderz

+0

Windowsサービスのデバッグはあまり面白くない:) – CrazyCoderz

答えて

1

私はなぜあなたに明確ではありませんよここでセマフォを使用していますが、間違って使用していることはほぼ確実です。 SendMessageへの呼び出しごとに新しいセマフォインスタンスを作成しています。また、WaitOneを呼び出してからRelease(2)を呼び出すと、最終的に取得するリリースよりも多くのリリースが発生します。それはおそらくあなたのInvalidOperationExceptionの原因です。

一度に1つのメッセージしか送信できないので、電子メールキューの処理を並列化することはできません。 Parallel.Foreachの中でそれを非同期的に実行しようとすると、ちょっとだけ無駄な合併症になります。

ThreadPool.QueueUserWorkItemのようなものを使い、一度に1つのメッセージを送信する単純なループを持つ方がよいでしょう。

List<EmailQueue> emailQueue = 
    _repository.Select<EmailQueue>().Where(x => x.EmailStatuses.EmailStatus == "Pending").ToList(); 

ThreadPool.QueueUserWorkItem(ProcessEmailQueue, emailQueue); 

void ProcessEmailQueue(object state) 
{ 
    List<EmailQueue> emailQueue = (List<EmailQueue>)state; 
    foreach (var message in EmailQueue) 
    { 
     // Format and send message here. 
    } 
} 

また、同じことをTaskとすることもできます。要点は、キューを順番に処理するためにただ1つのスレッドが必要なことです。一度に複数のメッセージを送信することはできませんので、Parallel.ForEachは何も良いことではありません。

EDIT:あなたは、複数の操作を行う必要がある場合

時に送信し、あなたはおそらく、あなたの元のコードを変更することができます。まず、クラススコープでセマフォを初期化します。

private static Semaphore mailSendSemaphore = new Semaphore(10, 10); 

はその後、あなたのSendMessage方法で:

bool SendMessage() 
{ 
    // acquire semaphore. This will block until there's a slot available. 
    mailSendSemaphore.WaitOne(); 
    try 
    { 
     // do all your processing here, including sending the message. 
     // use Send rather than SendAsync 
    } 
    finally 
    { 
     mailSendSemaphore.Release(); 
    } 
} 

SendAsyncを使用する必要はありません。

+0

あなたのお勧めのいくつかを試してみましょう – CrazyCoderz

+0

@CrazyCoderz:私の更新されたレスポンスを参照してください –

+0

これは実行されますが、私はブールの戻り値を得ることができません。 ThreadPool.QueueUserWorkItemを使用して(ProcessEmailQueue、emailQueue); – CrazyCoderz

2

によって抹消なっているようですメッセージは非同期呼び出しが既に進行中です:

メッセージ=非同期呼び出しはすでに進行中です。このメソッドを呼び出すには、完了またはキャンセルする必要があります。

これはdocumentationに同意:

2つの単純なオプション:

  • を送信するために、クライアントの固定数、およびメッセージのキューを作成します。キューが空になるまで、各クライアントが完了するたびにキューからメッセージを取得するようにします。 BlockingCollection<T>はこれに適しています。

  • メッセージごとにSmtpClientを新しく作成します。これにより、SMTPサーバー上でDOS攻撃を効果的に開始する可能性があります。これは理想的ではありません。

正直に言うと、あなたはとにかく送信するメッセージを待っているときにSendAsyncを使用している理由、それは本当にはっきりしていない...

+0

Asyncはサービスに戻るための応答のタイプ。メッセージが送信された場合はマークする必要があります。 – CrazyCoderz

+0

私はいくつかのものを手直ししてみましょう – CrazyCoderz

関連する問題