Apa praktik terbaik untuk menggunakan SmtpClient, SendAsync dan Dispose di bawah .NET 4.0


116

Saya agak bingung tentang cara mengelola SmtpClient sekarang karena dapat dibuang, terutama jika saya melakukan panggilan menggunakan SendAsync. Agaknya saya tidak harus memanggil Buang sampai SendAsync selesai. Tapi haruskah saya menyebutnya (misalnya, menggunakan "menggunakan"). Skenario ini adalah layanan WCF yang mengirimkan email secara berkala saat panggilan dibuat. Sebagian besar penghitungannya cepat, tetapi pengiriman email bisa memakan waktu beberapa detik atau lebih, jadi Async lebih disukai.

Apakah saya harus membuat SmtpClient baru setiap kali saya mengirim email? Haruskah saya membuatnya untuk seluruh WCF? Tolong!

Perbarui Jika ada perbedaan, setiap email selalu disesuaikan untuk pengguna. WCF dihosting di Azure dan Gmail digunakan sebagai pengirim surat.


1
Lihat posting ini tentang gambaran yang lebih besar tentang cara menangani IDisposable dan async: stackoverflow.com/questions/974945/…
Chris Haas

Jawaban:


139

Catatan: .NET 4.5 SmtpClient mengimplementasikan async awaitablemetode SendMailAsync. Untuk versi yang lebih rendah, gunakan SendAsyncseperti yang dijelaskan di bawah ini.


Anda harus selalu membuang IDisposablecontoh sedini mungkin. Dalam kasus panggilan async, ini adalah panggilan balik setelah pesan dikirim.

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);

Agak menjengkelkan karena SendAsynctidak menerima panggilan balik.


Bukankah baris terakhir harus 'menunggu'?
niico

20
Tidak ada kode ini yang ditulis sebelum awaittersedia. Ini adalah panggilan balik tradisional menggunakan penangan kejadian. awaitharus digunakan jika menggunakan yang lebih baru SendMailAsync.
TheCodeKing

3
SmtpException: Kegagalan pengiriman email .--> System.InvalidOperationException: Operasi asinkron tidak dapat dimulai saat ini. Operasi asinkron hanya dapat dimulai dalam penangan atau modul asinkron atau selama kejadian tertentu dalam siklus hidup Halaman. Jika pengecualian ini terjadi saat menjalankan Halaman, pastikan Halaman tersebut ditandai <% @ Halaman Async = "true"%>. Pengecualian ini juga dapat menunjukkan upaya untuk memanggil metode "async void", yang umumnya tidak didukung dalam pemrosesan permintaan ASP.NET. Sebaliknya, metode asynchronous harus mengembalikan sebuah Tugas, dan pemanggil harus menunggunya.
Mrchief

1
Apakah aman untuk diberikan nullsebagai parameter kedua SendAsync(...)?
jocull

167

Pertanyaan asli ditanyakan tentang .NET 4, tetapi jika membantu sejak .NET 4.5 SmtpClient mengimplementasikan metode async yang dapat menunggu SendMailAsync.

Akibatnya, untuk mengirim email secara asinkron adalah sebagai berikut:

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);
        }
    }
}

Lebih baik hindari menggunakan metode SendAsync.


Mengapa lebih baik menghindarinya? Saya rasa itu tergantung dari kebutuhan.
Jowen

14
SendMailAsync () adalah pembungkus di sekitar metode SendAsync (). async / await jauh lebih rapi dan elegan. Itu akan mencapai persyaratan yang persis sama.
Boris Lipschitz

2
@RodHartzell Anda selalu dapat menggunakan .ContinueWith ()
Boris Lipschitz

2
Apakah lebih baik menggunakan menggunakan - atau membuang - atau tidak ada perbedaan praktis? Bukankah mungkin dalam blok 'using' terakhir itu smtpClient dapat dibuang sebelum SendMailAsync dijalankan?
niico

6
MailMessagejuga harus dibuang.
TheCodeKing

16

Secara umum, benda IDisposable harus dibuang secepatnya; mengimplementasikan IDisposable pada sebuah objek dimaksudkan untuk mengkomunikasikan fakta bahwa kelas tersebut memiliki sumber daya mahal yang harus dilepaskan secara deterministik. Namun, jika membuat sumber daya itu mahal dan Anda perlu membuat banyak objek ini, mungkin lebih baik (secara performa) menyimpan satu instance dalam memori dan menggunakannya kembali. Hanya ada satu cara untuk mengetahui apakah itu membuat perbedaan: buat profil!

Re: membuang dan Async: Anda tidak dapat menggunakan dengan usingjelas. Sebagai gantinya Anda biasanya membuang objek di acara SendCompleted:

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

6

Oke, pertanyaan lama saya tahu. Tetapi saya menemukan ini sendiri ketika saya perlu menerapkan sesuatu yang serupa. Saya hanya ingin membagikan beberapa kode.

Saya mengulangi beberapa SmtpClients untuk mengirim beberapa email secara asynchronous. Solusi saya mirip dengan TheCodeKing, tetapi saya membuang objek callback sebagai gantinya. Saya juga meneruskan MailMessage sebagai userToken untuk mendapatkannya di acara SendCompleted sehingga saya juga dapat memanggil buang di situ. Seperti ini:

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
Apakah merupakan praktik terbaik untuk membuat SmtpClient baru untuk setiap email yang akan dikirim?
Martín Coll

1
Ya, untuk pengiriman asinkron, selama Anda membuang klien di callback ...
jmelhus

1
Terima kasih! dan hanya demi penjelasan singkat: www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
Martín Coll

1
Ini adalah salah satu jawaban paling sederhana dan akurat yang saya temukan di stackoverflow untuk fungsi smtpclient.sendAsync dan penanganan pembuangan terkaitnya. Saya menulis perpustakaan pengiriman surat massal asinkron. Karena saya mengirim 50+ pesan setiap beberapa menit, maka menjalankan metode pembuangan adalah langkah yang sangat penting bagi saya. Kode ini benar-benar membantu saya mencapai itu. Saya akan membalas jika saya menemukan beberapa bug dalam kode ini selama lingkungan multi threading.
vibs2006

1
Saya dapat mengatakan bahwa ini bukan pendekatan yang baik ketika Anda mengirim 100+ email dalam satu lingkaran kecuali Anda memiliki kemampuan untuk mengkonfigurasi server pertukaran (jika Anda menggunakan). Server mungkin mengeluarkan pengecualian seperti 4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel. Alih-alih, coba gunakan hanya satu contohSmtpClient
ibubi

6

Anda dapat melihat mengapa sangat penting untuk membuang SmtpClient dengan komentar berikut:

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();

Dalam skenario saya mengirim banyak email menggunakan Gmail tanpa membuang klien, saya biasa mendapatkan:

Pesan: Layanan tidak tersedia, menutup saluran transmisi. Respons server adalah: 4.7.0 Masalah Sistem Sementara. Coba lagi nanti (WS). oo3sm17830090pdb.64 - gsmtp


1
Terima kasih telah membagikan pengecualian Anda di sini karena saya mengirim Klien SMTP tanpa membuang sejauh ini. Meskipun saya menggunakan Server SMTP saya sendiri, tetapi praktik pemrograman yang baik harus selalu dipertimbangkan. Melihat dari kesalahan Anda, saya sekarang mendapat peringatan dan akan memperbaiki kode saya untuk menyertakan fungsi pembuangan untuk memastikan keandalan platform.
vibs2006
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.