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


116

特にSendAsyncを使用して呼び出しを行う場合、SmtpClientを使い捨てできるように管理する方法に少し戸惑っています。おそらく、SendAsyncが完了するまでDisposeを呼び出さないでください。しかし、私はこれを呼び出す必要があります(たとえば、 "using"を使用)。このシナリオは、呼び出しが行われると定期的に電子メールを送信するWCFサービスです。ほとんどの計算は高速ですが、電子メールの送信には1秒ほどかかる場合があるため、非同期の方が望ましいでしょう。

メールを送信するたびに新しいSmtpClientを作成する必要がありますか?WCF全体に対して作成する必要がありますか?助けて!

更新違いが生じた場合に備えて、各メールは常にユーザーに合わせてカスタマイズされます。WCFはAzureでホストされ、Gmailがメーラーとして使用されます。


1
IDisposableおよびasyncの処理方法に関する全体像については、この投稿を参照してください。stackoverflow.com/ questions / 974945 /
Chris Haas

回答:


139

注:.NET 4.5 SmtpClientを実装するasync awaitable方法SendMailAsync。以前のバージョンの場合はSendAsync、以下の説明に従って使用してください。


IDisposableインスタンスはできるだけ早く破棄する必要があります。非同期呼び出しの場合、これはメッセージが送信された後のコールバックです。

var message = new MailMessage("from", "to", "subject", "body"))
var client = new SmtpClient("host");
client.SendCompleted += (s, e) => {
                           client.Dispose();
                           message.Dispose();
                        };
client.SendAsync(message, null);

SendAsyncがコールバックを受け付けないのは少し面倒です。


最後の行は「待つ」べきではありませんか?
niico、2015年

19
このコードがawait利用可能になる前に書かれたものはありません。これは、イベントハンドラーを使用する従来のコールバックです。await新しいを使用する場合に使用する必要がありますSendMailAsync
TheCodeKing、2015年

3
SmtpException:メールの送信に失敗しました。--> System.InvalidOperationException:現在、非同期操作を開始できません。非同期操作は、非同期ハンドラーまたはモジュール内、またはページライフサイクルの特定のイベント中にのみ開始できます。ページの実行中にこの例外が発生した場合は、ページに<%@ Page Async = "true"%>のマークが付いていることを確認してください。この例外は、「async void」メソッドを呼び出す試みを示している場合もあります。これは、通常、ASP.NET要求処理ではサポートされていません。代わりに、非同期メソッドはタスクを返し、呼び出し元はそれを待つ必要があります。
Mrchief

1
nullの2番目のパラメーターとして提供しても安全SendAsync(...)ですか?
ジョクル

167

元の質問では.NET 4が求められましたが、それが.NET 4.5以降で役立つ場合、SmtpClientは非同期の待機可能なメソッドを実装します SendMailAsync

その結果、電子メールを非同期で送信するには、次のようにします。

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

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

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

SendAsyncメソッドを使用しないことをお勧めします。


なぜそれを避ける方が良いのですか?要件によると思います。
Jowen、2014年

14
SendMailAsync()は、とにかくSendAsync()メソッドのラッパーです。async / awaitは、すっきりとしてエレガントです。まったく同じ要件を達成します。
Boris Lipschitz、2014年

2
@RodHartzellは常に.ContinueWith()を使用できます
Boris Lipschitz

2
を使用するか、破棄するか、または実際的な違いはない方が良いですか?SendMailAsyncが実行される前にsmtpClientが破棄される可能性がある最後の「using」ブロックでは可能ではありませんか?
niico 2015年

6
MailMessageまた、処分する必要があります。
TheCodeKing、2015年

16

一般に、IDisposableオブジェクトはできるだけ早く破棄する必要があります。オブジェクトにIDisposableを実装することは、問題のクラスが確定的に解放されるべき高価なリソースを保持しているという事実を伝えることを目的としています。ただし、これらのリソースの作成にコストがかかり、これらのオブジェクトを大量に作成する必要がある場合は、1つのインスタンスをメモリに保持して再利用する方が(パフォーマンスに関して)優れている場合があります。それが違いを生むかどうかを知る唯一の方法があります。それをプロファイリングしてください!

Re:破棄と非同期:using明らかに使用できません。代わりに、通常はSendCompletedイベントでオブジェクトを破棄します。

var smtpClient = new SmtpClient();
smtpClient.SendCompleted += (s, e) => smtpClient.Dispose();
smtpClient.SendAsync(...);

6

わかりました、古い質問です。しかし、似たようなものを実装する必要があったとき、私はこれを自分で偶然見つけました。私はいくつかのコードを共有したかっただけです。

複数のメールを非同期に送信するために、いくつかのSmtpClientを繰り返し処理しています。私の解決策はTheCodeKingに似ていますが、代わりにコールバックオブジェクトを破棄しています。また、MailTokenをuserTokenとして渡して、SendCompletedイベントで取得しているため、それに対してdisposeを呼び出すこともできます。このような:

foreach (Customer customer in Customers)
{
    SmtpClient smtpClient = new SmtpClient(); //SmtpClient configuration out of this scope
    MailMessage message = new MailMessage(); //MailMessage configuration out of this scope

    smtpClient.SendCompleted += (s, e) =>
    {
        SmtpClient callbackClient = s as SmtpClient;
        MailMessage callbackMailMessage = e.UserState as MailMessage;
        callbackClient.Dispose();
        callbackMailMessage.Dispose();
    };

    smtpClient.SendAsync(message, message);
}

2
送信するメールごとに新しいSmtpClientを作成するのがベストプラクティスですか?
マルティン・コル

1
はい、非同期送信の場合、コールバックでクライアントを
破棄する限り

1
ありがとう!ちょうど簡単な説明のために:www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
マルティン・コル

1
これは、smtpclient.sendAsync関数とそれに関連する破棄処理の stackoverflowで見つけた最も単純で正確な答えの1つです。非同期バルクメール送信ライブラリを作成しました。数分ごとに50以上のメッセージを送信するため、disposeメソッドの実行は私にとって非常に重要なステップでした。このコードは、まさにそれを実現するのに役立ちました。マルチスレッド環境でこのコードにバグが見つかった場合に備えて返信します。
vibs2006

1
Exchangeサーバーを構成する機能がない場合(使用する場合)、ループで100通以上のメールを送信する場合は、この方法は適切ではないと言えます。サーバーはのような例外をスローする可能性があり4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channelます。代わりに、次のインスタンスを1つだけ使用してみてくださいSmtpClient
ibubi

6

次のコメントで、SmtpClientを破棄することが特に重要である理由を確認できます。

public class SmtpClient : IDisposable
   // Summary:
    //     Sends a QUIT message to the SMTP server, gracefully ends the TCP connection,
    //     and releases all resources used by the current instance of the System.Net.Mail.SmtpClient
    //     class.
    public void Dispose();

私のシナリオでは、クライアントを破棄せずにGmailを使用して複数のメールを送信していたため、以前は次のようになりました。

メッセージ:サービスを利用できません。伝送チャネルを閉じています。サーバーの応答は次のとおりです:4.7.0一時的なシステムの問題。後でもう一度やり直してください(WS)。oo3sm17830090pdb.64-gsmtp


1
これまでに処分せずにSMTPクライアントを送信していたため、ここで例外を共有していただきありがとうございます。私は自分のSMTPサーバーを使用していますが、優れたプログラミング方法を常に検討する必要があります。あなたのエラーから見て、私は今警告を受けました、そしてプラットフォームの信頼性を保証するために破棄関数を含むように私のコードを修正します。
vibs2006 2017年
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.