web-dev-qa-db-ja.com

ASP.NETで非同期に電子メールを送信する適切な方法...(私はそれを正しく行っていますか?)

ユーザーが私のウェブサイトに登録するとき、なぜアクティベーションメールを受け取るためにSMTPが通過するのを「待つ」必要があるのか​​わかりません。

このコードを非同期で起動することを決定しましたが、それは冒険でした。

私が次のような方法を持っていると想像してみましょう:

private void SendTheMail() { // Stuff }

私の最初のは..通っていた。これは私がしました:

Emailer mailer = new Emailer();
Thread emailThread = new Thread(() => mailer.SendTheMail());
emailThread.Start();

これは機能します...エラー処理機能をテストすることにしました。私はわざとweb.configのSMTPサーバーアドレスを壊して、それを試しました。恐ろしい結果は、IIS基本的にw3wp.exeで未処理の例外エラーが発生したことです(Windowsエラーでした!非常に極端です...)。 IISが再起動されたため、Webサイト上の誰もがセッションを消去しました。完全に許容できない結果です!

次に考えたのは、非同期デリゲートについていくつかの調査を行うことでした。 (上記のスレッドの例とは異なり)非同期デリゲート内で例外が処理されているため、これはうまく機能するようです。しかし、私はそれを間違っているのか、あるいはメモリリークを引き起こしているのか心配です。

これが私がやっていることです:

Emailer mailer = new Emailer();
AsyncMethodCaller caller = new AsyncMethodCaller(mailer.SendMailInSeperateThread);
caller.BeginInvoke(message, email.EmailId, null, null);
// Never EndInvoke... 

私はこれを正しく行っていますか?

20
Ralph N

私がここで賛成したたくさんの良いアドバイスがありました... IDisposableを使用することを忘れないようにすることなど(私は完全に知りませんでした)。また、コンテキストがないため、別のスレッドで手動でエラーをキャッチすることがいかに重要であるかを理解しました。私はELMAHにすべてを処理させるだけの理論に取り組んでいます。また、さらに調べてみると、私もメールメッセージでIDisposableを使用するのを忘れていたことがわかりました。

Richardへの回答として、エラーをキャッチしている限り、(最初の例で提案したように)スレッドソリューションが機能することがわかりますが、IISが完全に爆発するという事実には、まだ恐ろしいことがあります。そのエラーは捕捉されません。これは、ASP.NET/IISがそれを行うことを意味することは決してないことを示しています...何かが起こったときにIISを台無しにしないので、代わりに.BeginInvoke/delegatesを使い続けることに傾倒しています。間違っており、ASP.NETでより人気があるようです。

ASawyerに応答して、SMTPクライアントに.SendAsyncが組み込まれていることにまったく驚いていました。私はしばらくその解決策を試しましたが、それは私にとってはうまくいっていないようです。 SendAsyncを実行するコードのクライアントをスキップできますが、SendCompletedイベントが完了するまでページは「待機」します。私の目標は、メールがバックグラウンドで送信されている間にユーザーとページを進めることでした。私はまだ何か悪いことをしているのではないかと思っています...誰かがこれをやって来たら、彼らは自分で試してみたいと思うかもしれません。

ELMAH.MVCエラーロギングに加えて、100%非同期でメールを送信するための完全なソリューションを以下に示します。例2の拡張バージョンを使用することにしました。

public void SendThat(MailMessage message)
{
    AsyncMethodCaller caller = new AsyncMethodCaller(SendMailInSeperateThread);
    AsyncCallback callbackHandler = new AsyncCallback(AsyncCallback);
    caller.BeginInvoke(message, callbackHandler, null);
}

private delegate void AsyncMethodCaller(MailMessage message);

private void SendMailInSeperateThread(MailMessage message)
{
    try
    {
        SmtpClient client = new SmtpClient();
        client.Timeout = 20000; // 20 second timeout... why more?
        client.Send(message);
        client.Dispose();
        message.Dispose();

        // If you have a flag checking to see if an email was sent, set it here
        // Pass more parameters in the delegate if you need to...
    }
    catch (Exception e)
    {
         // This is very necessary to catch errors since we are in
         // a different context & thread
         Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
    }
}

private void AsyncCallback(IAsyncResult ar)
{
    try
    {
        AsyncResult result = (AsyncResult)ar;
        AsyncMethodCaller caller = (AsyncMethodCaller)result.AsyncDelegate;
        caller.EndInvoke(ar);
    }
    catch (Exception e)
    {
        Elmah.ErrorLog.GetDefault(null).Log(new Error(e));
        Elmah.ErrorLog.GetDefault(null).Log(new Error(new Exception("Emailer - This hacky asynccallback thing is puking, serves you right.")));
    }
}
25
Ralph N

.NET 4.5以降、SmtpClientは非同期待機可能メソッド SendMailAsync を実装しています。その結果、電子メールを非同期で送信するには、次のようにします。

public async Task SendEmail(string toEmailAddress, string emailSubject, string emailMessage)
{
    var message = new MailMessage();
    message.To.Add(toEmailAddress);

    message.Subject = emailSubject;
    message.Body = emailMessage;

    using (var smtpClient = new SmtpClient())
    {
        await smtpClient.SendMailAsync(message);
    }
} 
6
Boris Lipschitz

.NetのSmtpClientクラスとMailMessageクラスを使用している場合は、いくつかの点に注意してください。まず、送信時にエラーが発生することが予想されるため、トラップして処理します。次に、.Net 4では、これらのクラスにいくつかの変更があり、どちらもIDisposableを実装しています(3.5以降のMailMessage、4.0の新しいSmtpClient)。このため、SmtpClientとMailMessageの作成は、ブロックを使用してラップするか、明示的に破棄する必要があります。これは、一部の人々が気づいていない重大な変更です。

非同期送信を使用する場合の破棄の詳細については、このSO質問を参照してください:

。NET 4.0でSmtpClient、SendAsync、Disposeを使用するためのベストプラクティスは何ですか

4
hatchet

メールの送信に.Net SmtpClientを使用していますか? すでに非同期メッセージを送信できます

編集-Emailer mailer = new Emailer();がSmtpClientのラッパーでない場合、これは私が想像するほど役に立ちません。

3
asawyer

ここでスレッド化は間違ったオプションではありませんが、自分で例外を処理しないと、バブルアップしてプロセスがクラッシュします。それをどのスレッドで実行するかは関係ありません。

だからmailer.SendTheMail()の代わりにこれを試してください:

new Thread(() => { 
  try 
  {
    mailer.SendTheMail();
  }
  catch(Exception ex)
  {
    // Do something with the exception
  }
});

さらに可能であれば、SmtpClientの非同期機能を使用してください。ただし、例外を処理する必要があります。

.Net 4の新しいParalletタスクライブラリをご覧になることをお勧めします。これには、例外的なケースを処理できる追加機能があり、ASP.Netのスレッドプールで適切に機能します。

3
Richard

それでは、メールの送信のみを処理する個別のポーラー/サービスを用意してみませんか?したがって、データベース/メッセージキューへの書き込みにかかる時間だけで登録ポストバックを実行できるようにし、次のポーリング間隔まで電子メールの送信を遅らせます。

同じ問題を今考えているところですが、サーバーのポストバックリクエスト内で電子メールの送信を開始することさえ本当にしたくないと思っています。 Webページの配信の背後にあるプロセスは、できるだけ早くユーザーに応答を返すことに関心があるはずです。より多くの作業を実行しようとすると、遅くなります。

コマンドクエリ分離プリンシパル( http://martinfowler.com/bliki/CQRS.html )をご覧ください。 Martin Fowlerは、操作のコマンド部分では、クエリ部分で使用されるものとは異なるモデルを使用できると説明しています。このシナリオでは、コマンドは「ユーザーの登録」であり、クエリは大まかな類推を使用したアクティベーションメールです。関連する引用は、おそらく次のようになります。

個別のモデルとは、通常、おそらく異なる論理プロセスで実行される異なるオブジェクトモデルを意味します

また、CQRSに関するWikipediaの記事もお読みください( http://en.wikipedia.org/wiki/Command%E2%80%93query_separation )。これが強調する重要な点は次のとおりです。

明確なコーディングのルールではなく、プログラミングガイドラインとして明確に意図されている

つまり、コード、プログラムの実行、およびプログラマーの理解に役立つ場所で使用してください。これは良い例のシナリオです。

このアプローチには、マルチスレッドの問題と頭痛のすべてを解消できるという利点があります。

2
A. Murray

この方法を使用します

private void email(object parameters)
    {
        Array arrayParameters = new object[2];
        arrayParameters = (Array)parameters;
        string Email = (string)arrayParameters.GetValue(0);
        string subjectEmail = (string)arrayParameters.GetValue(1);
        if (Email != "[email protected]")
        {
            OnlineSearch OnlineResult = new OnlineSearch();
            try
            {
                StringBuilder str = new StringBuilder();
                MailMessage mailMessage = new MailMessage();

                //here we set the address
                mailMessage.From = fromAddress;
                mailMessage.To.Add(Email);//here you can add multiple emailid
                mailMessage.Subject = "";
                //here we set add bcc address
                //mailMessage.Bcc.Add(new MailAddress("[email protected]"));
                str.Append("<html>");
                str.Append("<body>");
                str.Append("<table width=720 border=0 align=left cellpadding=0 cellspacing=5>");

                str.Append("</table>");
                str.Append("</body>");
                str.Append("</html>");
                //To determine email body is html or not
                mailMessage.IsBodyHtml = true;
                mailMessage.Body = str.ToString();
                //file attachment for this e-mail message.
                Attachment attach = new Attachment();
                mailMessage.Attachments.Add(attach);
                mailClient.Send(mailMessage);
            }

    }


  protected void btnEmail_Click(object sender, ImageClickEventArgs e)
    {
        try
        {
            string To = txtEmailTo.Text.Trim();
            string[] parameters = new string[2];
            parameters[0] = To;
            parameters[1] = PropCase(ViewState["StockStatusSub"].ToString());
            Thread SendingThreads = new Thread(email);
            SendingThreads.Start(parameters);
            lblEmail.Visible = true;
            lblEmail.Text = "Email Send Successfully ";
        }
1
Rahul

私は自分のプロジェクトで同じ問題を処理しました:

最初にThreadを試しました:
-コンテキストを失いました
-例外処理の問題
-一般的に言って、ThreadはIIS ThreadPool

だから私は切り替えてasynchronouslyで試します:
-「非同期」は、asp.net Webアプリケーションではfakeです。キューコールを入れてコンテキストをスイープするだけです

そこで、Windowsサービスを作成し、SQLテーブルから値を取得します:happy end

したがって、迅速な解決策として、ajax側から非同期呼び出しでユーザーにfakeはいと伝えますが、mvcコントローラーで送信ジョブを続行します

1
asdf_enel_hak

リークを検出したい場合は、次のようなプロファイラーを使用する必要があります。

http://memprofiler.com/

私はあなたのソリューションで何もwrongを見ていませんが、この質問が主観的に閉じられることをほぼ保証できます。

他の1つのオプションは、jQueryを使用してサーバーおよびspark電子メールフローを呼び出すことです。これにより、UIがロックされません。

幸運を!

マット

0
Matt Cashatt