แนวทางปฏิบัติที่ดีที่สุดสำหรับการใช้ SmtpClient, SendAsync และ Dispose ภายใต้. NET 4.0 คืออะไร


116

ฉันงงเล็กน้อยเกี่ยวกับวิธีจัดการ SmtpClient ตอนนี้มันเป็นแบบใช้แล้วทิ้งโดยเฉพาะอย่างยิ่งถ้าฉันโทรโดยใช้ SendAsync ฉันไม่ควรเรียก Dispose จนกว่า SendAsync จะเสร็จสมบูรณ์ แต่ฉันควรจะเรียกมันว่า (เช่นใช้ "ใช้") สถานการณ์คือบริการ WCF ซึ่งส่งอีเมลเป็นระยะเมื่อมีการโทร การคำนวณส่วนใหญ่เป็นไปอย่างรวดเร็ว แต่การส่งอีเมลอาจใช้เวลาสักครู่ดังนั้น Async จึงเป็นที่ต้องการ

ฉันควรสร้าง SmtpClient ใหม่ทุกครั้งที่ส่งอีเมลหรือไม่ ฉันควรสร้างหนึ่งสำหรับ WCF ทั้งหมดหรือไม่ ช่วยด้วย!

อัปเดตในกรณีที่สร้างความแตกต่างอีเมลแต่ละฉบับจะปรับแต่งให้เหมาะกับผู้ใช้เสมอ WCF โฮสต์บน Azure และ Gmail ถูกใช้เป็นจดหมาย


1
ดูโพสต์นี้เกี่ยวกับภาพรวมเกี่ยวกับวิธีจัดการ IDisposable และ async: stackoverflow.com/questions/974945/…
Chris Haas

คำตอบ:


139

.NET 4.5 SmtpClient ดำเนินหมายเหตุ: วิธีการasync awaitable SendMailAsyncสำหรับเวอร์ชันที่ต่ำกว่าให้ใช้SendAsyncตามที่อธิบายไว้ด้านล่าง


คุณควรกำจัดIDisposableอินสแตนซ์โดยเร็วที่สุดเสมอ ในกรณีของการโทรแบบ async จะอยู่ในการโทรกลับหลังจากส่งข้อความแล้ว

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

20
ไม่มีรหัสนี้ถูกเขียนขึ้นก่อนที่จะawaitมีอยู่ นี่คือการโทรกลับแบบดั้งเดิมโดยใช้ตัวจัดการเหตุการณ์ ควรจะใช้ถ้าใช้ใหม่await SendMailAsync
TheCodeKing

3
SmtpException: การส่งอีเมลล้มเหลว -> System.InvalidOperationException: ไม่สามารถเริ่มการทำงานแบบอะซิงโครนัสได้ในขณะนี้ การดำเนินการแบบอะซิงโครนัสสามารถเริ่มต้นได้ภายในตัวจัดการหรือโมดูลแบบอะซิงโครนัสหรือในช่วงเหตุการณ์บางอย่างในวงจรชีวิตของเพจ หากข้อยกเว้นนี้เกิดขึ้นขณะเรียกใช้เพจตรวจสอบให้แน่ใจว่าเพจถูกทำเครื่องหมาย <% @ Page Async = "true"%> ข้อยกเว้นนี้อาจบ่งบอกถึงความพยายามที่จะเรียกเมธอด "async void" ซึ่งโดยทั่วไปไม่ได้รับการสนับสนุนในการประมวลผลคำขอ ASP.NET แต่วิธีการอะซิงโครนัสควรส่งคืนงานและผู้โทรควรรอ
Mrchief

1
มันมีความปลอดภัยเพื่อให้nullเป็นพารามิเตอร์ที่สองเพื่อSendAsync(...)?
jocull

167

คำถามเดิมถูกถามสำหรับ. NET 4 แต่ถ้ามันช่วยได้ในขณะที่. NET 4.5 SmtpClient ใช้วิธีการ async ที่รอ 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

14
SendMailAsync () เป็น wrapper รอบ SendAsync () วิธีการต่อไป async / await เป็นวิธีที่เรียบร้อยและสง่างามมากขึ้น มันจะบรรลุความต้องการเดียวกันทุกประการ
Boris Lipschitz

2
@RodHartzell คุณสามารถใช้ได้เสมอ ContinueWith ()
Boris Lipschitz

2
จะดีกว่าถ้าใช้โดยใช้ - หรือทิ้ง - หรือไม่มีความแตกต่างในทางปฏิบัติ? เป็นไปไม่ได้ในบล็อก 'การใช้' ครั้งสุดท้ายที่ smtpClient สามารถกำจัดได้ก่อนที่ SendMailAsync จะดำเนินการ?
niico

6
MailMessageควรกำจัดด้วย
TheCodeKing

16

โดยทั่วไปแล้วควรกำจัดวัตถุที่สามารถกำจัดได้โดยเร็วที่สุด การนำ IDisposable ไปใช้กับอ็อบเจ็กต์มีจุดมุ่งหมายเพื่อสื่อสารความจริงที่ว่าคลาสที่มีปัญหามีทรัพยากรราคาแพงที่ควรได้รับการปลดปล่อยอย่างกำหนด อย่างไรก็ตามหากการสร้างทรัพยากรเหล่านั้นมีราคาแพงและคุณจำเป็นต้องสร้างวัตถุเหล่านี้จำนวนมากอาจเป็นการดีกว่า (ประสิทธิภาพที่ชาญฉลาด) ในการเก็บอินสแตนซ์หนึ่งรายการไว้ในหน่วยความจำและนำกลับมาใช้ใหม่ มีทางเดียวที่จะรู้ว่าสิ่งนั้นสร้างความแตกต่างหรือไม่: โปรไฟล์มัน

Re: การกำจัดและ Async: คุณไม่สามารถใช้usingอย่างชัดเจน โดยทั่วไปคุณจะทิ้งวัตถุในเหตุการณ์ SendCompleted แทน:

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

6

โอเคคำถามเก่าที่ฉันรู้ แต่ฉันสะดุดกับสิ่งนี้เมื่อฉันต้องการใช้สิ่งที่คล้ายกัน ฉันแค่อยากจะแชร์โค้ด

ฉันกำลังทำซ้ำหลาย ๆ SmtpClients เพื่อส่งจดหมายหลายฉบับแบบอะซิงโครนัส วิธีแก้ปัญหาของฉันคล้ายกับ TheCodeKing แต่ฉันกำลังกำจัดวัตถุเรียกกลับแทน ฉันยังส่ง MailMessage เป็น userToken เพื่อรับมันในเหตุการณ์ SendCompleted ดังนั้นฉันจึงสามารถเรียกใช้การกำจัดได้เช่นกัน แบบนี้:

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 ใหม่สำหรับการส่งอีเมลแต่ละฉบับหรือไม่
Martín Coll

1
ใช่สำหรับการส่งแบบอะซิงโครนัสตราบเท่าที่คุณกำจัดไคลเอนต์ในการติดต่อกลับ ...
jmelhus

1
ขอบคุณ! และเพื่อประโยชน์ในการอธิบายสั้น ๆ : www.codefrenzy.net/2012/01/30/how-asynchronous-is-smtpclient-sendasync
Martín Coll

1
นี่เป็นหนึ่งในคำตอบที่ง่ายและแม่นยำที่สุดที่ฉันพบใน stackoverflow สำหรับฟังก์ชันsmtpclient.sendAsyncและการจัดการกำจัดที่เกี่ยวข้อง ฉันเขียนไลบรารีการส่งอีเมลจำนวนมากแบบอะซิงโครนัส เนื่องจากฉันส่งข้อความมากกว่า 50 ข้อความทุกๆสองสามนาทีดังนั้นการใช้วิธีการกำจัดจึงเป็นขั้นตอนที่สำคัญมากสำหรับฉัน รหัสนี้ช่วยให้ฉันบรรลุเป้าหมายนั้น ฉันจะตอบกลับในกรณีที่พบข้อบกพร่องบางอย่างในโค้ดนี้ระหว่างสภาพแวดล้อมการทำงานแบบมัลติเธรด
vibs2006

1
ฉันสามารถพูดได้ว่ามันไม่ใช่แนวทางที่ดีเมื่อคุณส่งอีเมลมากกว่า 100 ฉบับในลูปเว้นแต่คุณจะมีความสามารถในการกำหนดค่าเซิร์ฟเวอร์แลกเปลี่ยน (ถ้าคุณใช้) เซิร์ฟเวอร์อาจมีข้อยกเว้นเช่น4.3.2 The maximum number of concurrent connections has exceeded a limit, closing trasmission channel. ลองใช้เพียงอินสแตนซ์เดียวของ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
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.