2010-08-04 126 views
30

我正在開發一個應用程序,用戶點擊/按下窗口中的某個按鈕,應用程序會執行一些檢查並確定是否發送一些電子郵件,然後用消息顯示另一個窗口。在C#中異步發送電子郵件?

我的問題是,發送2封電子郵件顯着降低了處理速度,並且對於某些(〜8)秒,第一個窗口在發送時會凍結。

有沒有什麼辦法讓我可以在後臺發送這些電子郵件並立即顯示下一個窗口?

請不要用「使用X類」或「只使用X方法」來限制您的答案,因爲我對語言還不是太熟悉,而且還有一些更多的信息會受到高度讚賞。

謝謝。

+0

您的應用程序是否需要對發送電子郵件的結果(成功,失敗,其他)做任何事情?這可能會影響您是否需要等待或查找發送郵件的結果(以及因此*您如何這樣做),或者如果它更像是一種「隨時忘記」的事情。 – JaredReisinger 2010-08-04 18:16:59

+0

並非如此,它更像是一個讓你忘卻的東西。我認爲這很簡單,但對我來說顯然不是那麼重要。我現在正在查看不同的答案並測試結果。 – 2010-08-04 18:24:15

+0

如果你看我的答案,我已經給你一個小的(但應該工作 - 雖然沒有測試過!)例如如何發送電子郵件不僅是異步的,但如何處理結果*和*顯示反饋到用戶界面。 – James 2010-08-04 18:27:55

回答

54

從.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); 
    } 
} 
+0

今天的標準是最好的答案,因爲它是唯一支持異步/等待的。 – 2014-06-06 17:16:44

+0

此代碼不能用作主機和發件人地址。 – Elisabeth 2015-03-18 22:38:18

+13

問題是這段代碼仍然會等待郵件發送。如果你編輯它來添加一個調用這個方法在一個單獨的線程中啓動它將是很好的。 – JCabello 2015-05-27 05:08:10

9

,這不是太複雜,只需發送一個單獨的線程消息:

using System.Net.Mail; 

Smtp.SendAsync(message); 

或者,如果你想建立的獨立的線程,而不是整個消息,而只是將其發送異步:

using System.Threading; 
using System.Net.Mail; 

var sendMailThread = new Thread(() => { 
    var message=new MailMessage(); 
    message.From="from e-mail"; 
    message.To="to e-mail"; 
    message.Subject="Message Subject"; 
    message.Body="Message Body"; 

    SmtpMail.SmtpServer="SMTP Server Address"; 
    SmtpMail.Send(message); 
}); 

sendMailThread.Start(); 
+5

[ObsoleteAttribute(「推薦的替代方法是System.Net.Mail.SmtpClient。http://go.microsoft.com/fwlink/?linkid=14202」)] – Andrey 2010-08-04 18:09:32

+6

也建議對長時間運行的進程創建線程,而不是針對異步任務 – Andrey 2010-08-04 18:10:51

1

你想要做的就是在一個單獨的線程上運行電子郵件任務,這樣主代碼可以繼續處理,而另一個線程完成電子郵件的工作。

這裏是如何做到這一點的教程: Threading Tutorial C#

8

SmtpClient.SendAsync Method

樣品

using System; 
using System.Net; 
using System.Net.Mail; 
using System.Net.Mime; 
using System.Threading; 
using System.ComponentModel; 
namespace Examples.SmptExamples.Async 
{ 
    public class SimpleAsynchronousExample 
    { 
     static bool mailSent = false; 
     private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e) 
     { 
      // Get the unique identifier for this asynchronous operation. 
      String token = (string) e.UserState; 

      if (e.Cancelled) 
      { 
       Console.WriteLine("[{0}] Send canceled.", token); 
      } 
      if (e.Error != null) 
      { 
       Console.WriteLine("[{0}] {1}", token, e.Error.ToString()); 
      } else 
      { 
       Console.WriteLine("Message sent."); 
      } 
      mailSent = true; 
     } 
     public static void Main(string[] args) 
     { 
      // Command line argument must the the SMTP host. 
      SmtpClient client = new SmtpClient(args[0]); 
      // Specify the e-mail sender. 
      // Create a mailing address that includes a UTF8 character 
      // in the display name. 
      MailAddress from = new MailAddress("[email protected]", 
       "Jane " + (char)0xD8+ " Clayton", 
      System.Text.Encoding.UTF8); 
      // Set destinations for the e-mail message. 
      MailAddress to = new MailAddress("[email protected]"); 
      // Specify the message content. 
      MailMessage message = new MailMessage(from, to); 
      message.Body = "This is a test e-mail message sent by an application. "; 
      // Include some non-ASCII characters in body and subject. 
      string someArrows = new string(new char[] {'\u2190', '\u2191', '\u2192', '\u2193'}); 
      message.Body += Environment.NewLine + someArrows; 
      message.BodyEncoding = System.Text.Encoding.UTF8; 
      message.Subject = "test message 1" + someArrows; 
      message.SubjectEncoding = System.Text.Encoding.UTF8; 
      // Set the method that is called back when the send operation ends. 
      client.SendCompleted += new 
      SendCompletedEventHandler(SendCompletedCallback); 
      // The userState can be any object that allows your callback 
      // method to identify this send operation. 
      // For this example, the userToken is a string constant. 
      string userState = "test message1"; 
      client.SendAsync(message, userState); 
      Console.WriteLine("Sending message... press c to cancel mail. Press any other key to exit."); 
      string answer = Console.ReadLine(); 
      // If the user canceled the send, and mail hasn't been sent yet, 
      // then cancel the pending operation. 
      if (answer.StartsWith("c") && mailSent == false) 
      { 
       client.SendAsyncCancel(); 
      } 
      // Clean up. 
      message.Dispose(); 
      Console.WriteLine("Goodbye."); 
     } 
    } 
} 
+2

-1 OP特別詢問你是否不只是指定他們需要使用什麼方法,他們想要一個例子。 – James 2010-08-04 18:24:34

+4

@詹姆斯去鏈接,那裏有例子。 「示例[+]以下代碼示例演示如何調用此方法。」 – Andrey 2010-08-04 18:27:01

+0

仔細觀察。 ** message.Dispose()**是詹姆斯在他的回覆中糾正的真正罪魁禍首。 – vibs2006 2016-12-29 02:04:27

2

使用SmtpClient類和使用方法​​在System.Net.Mail命名空間。

5

只是因爲這是一個有點含糊......我將簡要...

有很多方法可以做到同步或並行工作,在C#/。NET等

最快方法來做你想做的就是使用後臺工作線程,這將避免鎖定你的用戶界面。

與後臺工作線程提示:你不能直接從他們更新UI(線程親和力和編組站只是你要學會處理事情...)

另一個要考慮的......如果你使用標準的System.Net.Mail類型的東西來發送電子郵件......要小心你如何制定你的邏輯。如果你用某種方法將其全部隔離並反覆調用,那麼每次都可能不得不拆卸並重建與郵件服務器的連接,並且認證等所涉及的延遲仍會使整個事件不必要地減速。在可能的情況下,通過單個打開的連接向郵件服務器發送多個電子郵件。

4

試試這個:

var client = new System.Net.Mail.SmtpClient("smtp.server"); 
var message = new System.Net.Mail.MailMessage() { /* provide its properties */ }; 
client.SendAsync(message, null); 
+3

我試過這個解決方案,但SendAsync至少需要2個參數。我使用了正確的超載,但電子郵件沒有被髮送,我試過調試,並且在SendAsync行中收件人地址,主題,正文等都是正確的。我不知道爲什麼Send沒有發送它們。 – 2010-08-04 18:56:02

+1

$ eton-b:對於語法錯誤感到抱歉。我修好了它。 – kbrimington 2010-08-04 20:15:07

1

使用.NET 4.0的Task Parallel Library,你可以這樣做:

Parllel.Invoke(() => { YourSendMailMethod(); }); 

而且,看到cristina manu's博客帖子大約Parallel.Invoke()與明確的任務管理。

+1

這不起作用。「Invoke」函數在內部的所有操作完成之前不會返回。 – Gabe 2010-08-04 20:25:43

+1

Crud ...當然你是對的。我正在考慮'Task.Factory.StartNew()'。 – JaredReisinger 2010-08-04 22:12:49

22

由於它是一個小的工作單元,因此它應該使用ThreadPool.QueueUserWorkItem作爲它的線程方面。如果您使用SmtpClient類發送郵件,則可以處理SendCompleted事件以向用戶提供反饋。

ThreadPool.QueueUserWorkItem(t => 
{ 
    SmtpClient client = new SmtpClient("MyMailServer"); 
    MailAddress from = new MailAddress("[email protected]", "My Name", System.Text.Encoding.UTF8); 
    MailAddress to = new MailAddress("[email protected]"); 
    MailMessage message = new MailMessage(from, to); 
    message.Body = "The message I want to send."; 
    message.BodyEncoding = System.Text.Encoding.UTF8; 
    message.Subject = "The subject of the email"; 
    message.SubjectEncoding = System.Text.Encoding.UTF8; 
    // Set the method that is called back when the send operation ends. 
    client.SendCompleted += new SendCompletedEventHandler(SendCompletedCallback); 
    // The userState can be any object that allows your callback 
    // method to identify this send operation. 
    // For this example, I am passing the message itself 
    client.SendAsync(message, message); 
}); 

private static void SendCompletedCallback(object sender, AsyncCompletedEventArgs e) 
{ 
     // Get the message we sent 
     MailMessage msg = (MailMessage)e.UserState; 

     if (e.Cancelled) 
     { 
      // prompt user with "send cancelled" message 
     } 
     if (e.Error != null) 
     { 
      // prompt user with error message 
     } 
     else 
     { 
      // prompt user with message sent! 
      // as we have the message object we can also display who the message 
      // was sent to etc 
     } 

     // finally dispose of the message 
     if (msg != null) 
      msg.Dispose(); 
} 

通過創建一個新的SMTP客戶端,每次這將允許您同時發送電子郵件。

+1

所以我正在嘗試這個答案,因爲它對我來說似乎是最可行的(簡單/類似於我使用的)。但是,究竟是什麼用作「userState」?我必須使用線程嗎?我只是改變了我的發送方法: string someString =「Message」; smtp.SendAsync(message,someString); 無濟於事。我會實施所有的解決方案,看看我做錯了什麼。 – 2010-08-04 18:38:23

+2

@Eton如果你看看我已經指出了userState變量的例子。它是在事件引發時傳遞給回調方法的對象。在這個例子中,我使用它作爲唯一的標識符,但基本上你可以通過任何你想要的東西進入它。不,它不是必需的,如果你不需要在回調方法中使用它,那麼只需傳入null即可。 – James 2010-08-04 19:11:15

+1

@Eton - 不,你根本沒有*可以使用線程。如果您將電子郵件發送代碼從QueueUserWorkItem調用中移出,並將其直接放在按鈕的單擊事件之後,則它應該與您每次創建單獨的SmtpClient一樣工作,並且使用SendAsync發送電子郵件(它贏得了「 t阻止用戶界面)。 – James 2010-08-04 19:13:58

4

這裏是一個火災和使用的.Net 4.5.2+異步一起忘記的方法:

BackgroundTaskRunner.FireAndForgetTaskAsync(async() => 
{ 
    SmtpClient smtpClient = new SmtpClient(); // using configuration file settings 
    MailMessage message = new MailMessage(); // TODO: Initialize appropriately 
    await smtpClient.SendMailAsync(message); 
}); 

其中BackgroundTaskRunner是:

public static class BackgroundTaskRunner 
{  
    public static void FireAndForgetTask(Action action) 
    { 
     HostingEnvironment.QueueBackgroundWorkItem(cancellationToken => // .Net 4.5.2+ required 
     { 
      try 
      { 
       action(); 
      } 
      catch (Exception e) 
      { 
       // TODO: handle exception 
      } 
     }); 
    } 

    /// <summary> 
    /// Using async 
    /// </summary> 
    public static void FireAndForgetTaskAsync(Func<Task> action) 
    { 
     HostingEnvironment.QueueBackgroundWorkItem(async cancellationToken => // .Net 4.5.2+ required 
     { 
      try 
      { 
       await action(); 
      } 
      catch (Exception e) 
      { 
       // TODO: handle exception 
      } 
     }); 
    } 
} 

像Azure App Services上的魅力一樣工作。