用于发送电子邮件的 C# Windows 服务中的内存泄漏
这似乎是当今一个非常流行的问题,但我似乎找不到问题的解决方案。
我用 C# 创建了一个简单的 Windows 服务来发送电子邮件。除了内存占用之外,该应用程序运行良好。该应用程序的前端基于 Web,并且该服务通过在目录中创建的文本文件进行排队。读取文本文件后,该服务从 MS SQL 数据库收集新闻通讯信息和电子邮件地址,并开始每 4 秒发送 1 封电子邮件。通过任务管理器观察服务运行情况时,您可以看到 CPU 使用率每 4 秒就会增加一次,但立即又回落。另一方面,内存似乎不是每封电子邮件都会增加 50-75k,而是每 3-4 封电子邮件都会增加 50-75k。这将继续增加,直到发送完所有电子邮件。我刚刚发出了大约。 2100封邮件,内存占用高达100MB。我注意到的另一件事是,发送所有电子邮件后,内存使用量将保持在这个总数,直到我重新启动服务。服务空闲时,内存运行在6500k左右。有人对我如何在邮件完成后减少和处理内存使用有任何建议吗?我的代码如下。任何帮助将不胜感激..
namespace NewsMailer
{
public partial class NewsMailer : ServiceBase
{
private FileSystemWatcher dirWatcher;
private static string filePath = @"E:\Intranets\Internal\Newsletter\EmailQueue";
private static string attachPath = @"E:\Intranets\Internal\Newsletter\Attachments";
private string newsType = String.Empty;
private string newsSubject = String.Empty;
private string newsContent = String.Empty;
private string userName = String.Empty;
private string newsAttachment = "";
private int newsID = 0;
private int emailSent = 0;
private int emailError = 0;
public NewsMailer()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
dirWatcher = new FileSystemWatcher();
dirWatcher.Path = filePath;
dirWatcher.Created += new FileSystemEventHandler(ReadText);
dirWatcher.EnableRaisingEvents = true;
}
protected override void OnStop()
{
dirWatcher.EnableRaisingEvents = false;
dirWatcher.Dispose();
}
private void ClearVar()
{
newsType = String.Empty;
newsSubject = String.Empty;
newsContent = String.Empty;
userName = String.Empty;
newsAttachment = "";
newsID = 0;
emailSent = 0;
emailError = 0;
}
private void ReadText(object sender, FileSystemEventArgs e)
{
ClearVar();
SetLimits();
string txtFile = filePath + @"\QueueEmail.txt";
StreamReader sr = new StreamReader(txtFile);
string txtLine = String.Empty;
try
{
while ((txtLine = sr.ReadLine()) != null)
{
string[] lineCpl = txtLine.Split('§');
newsType = lineCpl[0];
userName = lineCpl[1];
newsID = Convert.ToInt32(lineCpl[2]);
}
}
catch (IOException ex)
{
SendExByMail("ReadText() IO Error", ex);
}
catch (Exception ex)
{
SendExByMail("ReadText() General Error", ex);
}
finally
{
sr.Close();
sr.Dispose();
}
GetNews();
}
[DllImport("kernel32.dll")]
public static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);
private void SetLimits()
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, -1, -1);
}
private void DeleteText()
{
try
{
File.Delete(filePath + @"\QueueEmail.txt");
}
catch (IOException ex)
{
SendExByMail("DeleteText() IO Error", ex);
}
catch (Exception ex)
{
SendExByMail("DeleteText() General Error", ex);
}
}
private void GetNews()
{
string connectionString = ConfigurationManager.ConnectionStrings["contacts"].ConnectionString;
SqlConnection conn = new SqlConnection(connectionString);
string sqlSELECT = "SELECT newsSubject, newsContents, username, attachment FROM newsArchive " +
"WHERE ID = " + newsID;
SqlCommand comm = new SqlCommand(sqlSELECT, conn);
try
{
conn.Open();
using (SqlDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
newsSubject = reader[0].ToString();
newsContent = reader[1].ToString();
userName = reader[2].ToString();
newsAttachment = reader[3].ToString();
}
reader.Dispose();
}
}
catch (SqlException ex)
{
SendExByMail("GetNews() SQL Error", ex);
}
catch (Exception ex)
{
SendExByMail("GetNews() General Error", ex);
}
finally
{
comm.Dispose();
conn.Dispose();
}
DeleteText();
GetAddress();
}
private void GetAddress()
{
string connectionString = ConfigurationManager.ConnectionStrings["contacts"].ConnectionString;
SqlConnection conn = new SqlConnection(connectionString);
string sqlSELECT = String.Empty;
if (newsType == "custom")
sqlSELECT = "SELECT DISTINCT email FROM custom";
else
sqlSELECT = "SELECT DISTINCT email FROM contactsMain WHERE queued = 'True'";
SqlCommand comm = new SqlCommand(sqlSELECT, conn);
try
{
conn.Open();
using (SqlDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
try
{
if (CheckEmail(reader[0].ToString()) == true)
{
SendNews(reader[0].ToString());
Thread.Sleep(4000);
emailSent++;
}
else
{
SendInvalid(reader[0].ToString());
emailError++;
}
}
catch (SmtpException ex)
{
SendExByMail("NewsLetter Smtp Error", reader[0].ToString(), ex);
emailError++;
}
catch (Exception ex)
{
SendExByMail("Send NewsLetter General Error", reader[0].ToString(), ex);
emailError++;
}
finally
{
UnqueueEmail(reader[0].ToString());
}
}
reader.Dispose();
}
}
catch (SqlException ex)
{
SendExByMail("GetAddress() SQL Error", ex);
}
catch (Exception ex)
{
SendExByMail("GetAddress() General Error", ex);
}
finally
{
comm.Dispose();
conn.Dispose();
}
SendConfirmation();
}
private bool CheckEmail(string emailAddy)
{
bool returnValue = false;
string regExpress = @"^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$";
Match verifyE = Regex.Match(emailAddy, regExpress);
if (verifyE.Success)
returnValue = true;
return returnValue;
}
private void SendNews(string emailAddy)
{
string today = DateTime.Today.ToString("MMMM d, yyyy");
using (MailMessage message = new MailMessage())
{
SmtpClient smtpClient = new SmtpClient();
MailAddress fromAddress = new MailAddress("");
message.From = fromAddress;
message.To.Add(emailAddy);
message.Subject = newsSubject;
if (newsAttachment != "")
{
Attachment wusaAttach = new Attachment(attachPath + newsAttachment);
message.Attachments.Add(wusaAttach);
}
message.IsBodyHtml = true;
#region Message Body
message.Body = "";
#endregion
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
}
private void UnqueueEmail(string emailAddy)
{
string connectionString = ConfigurationManager.ConnectionStrings["contacts"].ConnectionString;
SqlConnection conn = new SqlConnection(connectionString);
string sqlStatement = String.Empty;
if (newsType == "custom")
sqlStatement = "UPDATE custom SET queued = 'False' WHERE email LIKE '" + emailAddy + "'";
else
sqlStatement = "UPDATE contactsMain SET queued = 'False' WHERE email LIKE '" + emailAddy + "'";
SqlCommand comm = new SqlCommand(sqlStatement, conn);
try
{
conn.Open();
comm.ExecuteNonQuery();
}
catch (SqlException ex)
{
SendExByMail("UnqueueEmail() SQL Error", ex);
}
catch (Exception ex)
{
SendExByMail("UnqueueEmail() General Error", ex);
}
finally
{
comm.Dispose();
conn.Dispose();
}
}
private void SendConfirmation()
{
SmtpClient smtpClient = new SmtpClient();
using (MailMessage message = new MailMessage())
{
MailAddress fromAddress = new MailAddress("");
MailAddress toAddress = new MailAddress();
message.From = fromAddress;
message.To.Add(toAddress);
//message.CC.Add(ccAddress);
message.Subject = "Your Newsletter Mailing Has Completed";
message.IsBodyHtml = true;
message.Body = "Total Emails Sent: " + emailSent +
"<br />Total Email Errors: " + emailError +
"<br />Contact regarding email errors if any were found";
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
ClearVar();
System.GC.Collect();
}
private void SendInvalid(string emailAddy)
{
SmtpClient smtpClient = new SmtpClient();
using (MailMessage message = new MailMessage())
{
MailAddress fromAddress = new MailAddress("");
MailAddress toAddress = new MailAddress("");
message.From = fromAddress;
message.To.Add(toAddress);
//message.CC.Add(ccAddress);
message.Subject = "Invalid Email Address";
message.IsBodyHtml = true;
message.Body = "An invalid email address has been found, please check the following " +
"email address:<br />" + emailAddy;
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
}
private void SendExByMail(string subject, Exception ex)
{
SmtpClient smtpClient = new SmtpClient();
using (MailMessage message = new MailMessage())
{
MailAddress fromAddress = new MailAddress("");
MailAddress toAddress = new MailAddress("");
message.From = fromAddress;
message.To.Add(toAddress);
//message.CC.Add(ccAddress);
message.Subject = subject;
message.IsBodyHtml = true;
message.Body = "An Error Has Occurred: <br />Exception: <br />" + ex.ToString();
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
}
private void SendExByMail(string subject, string body, Exception ex)
{
SmtpClient smtpClient = new SmtpClient();
using (MailMessage message = new MailMessage())
{
MailAddress fromAddress = new MailAddress("", "MailerService");
MailAddress toAddress = new MailAddress("");
message.From = fromAddress;
message.To.Add(toAddress);
//message.CC.Add(ccAddress);
message.Subject = subject;
message.IsBodyHtml = true;
message.Body = "An Error Has Occurred:<br /><br />" + body + "<br /><br />Exception: <br />" + ex.ToString();
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
}
}
}
This seems to be a pretty popular problem/question these days but I cannot seem to find a solution to the problem.
I have created a simple windows service in c# for sending out emails. The app works great except for it's memory usage. The front end of the app is web based and the service is queued by a text file being created in a directory. After reading the text file the service gathers the newsletter info and email addresses from MS SQL db and commences to send out 1 email every 4 seconds. While watching the service run through task manager, you can see the cpu usage bump up every 4 seconds but immediately drop back down. The memory on the other hand seems to bump up not every email but every 3-4 emails by 50-75k. This will continue to increment until all emails are sent. I just sent out approx. 2100 emails and the memory usage was up to 100MB. Another thing I have noticed is that after all emails are sent, the memory usage will hold at this total until I restart the service. When the service is idling, the memory runs at about 6500k. Anyone have any suggestions as to how I can get this memory usage down and disposed of after the mailings complete? My code is below. Any help would be greatly appreciated..
namespace NewsMailer
{
public partial class NewsMailer : ServiceBase
{
private FileSystemWatcher dirWatcher;
private static string filePath = @"E:\Intranets\Internal\Newsletter\EmailQueue";
private static string attachPath = @"E:\Intranets\Internal\Newsletter\Attachments";
private string newsType = String.Empty;
private string newsSubject = String.Empty;
private string newsContent = String.Empty;
private string userName = String.Empty;
private string newsAttachment = "";
private int newsID = 0;
private int emailSent = 0;
private int emailError = 0;
public NewsMailer()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
dirWatcher = new FileSystemWatcher();
dirWatcher.Path = filePath;
dirWatcher.Created += new FileSystemEventHandler(ReadText);
dirWatcher.EnableRaisingEvents = true;
}
protected override void OnStop()
{
dirWatcher.EnableRaisingEvents = false;
dirWatcher.Dispose();
}
private void ClearVar()
{
newsType = String.Empty;
newsSubject = String.Empty;
newsContent = String.Empty;
userName = String.Empty;
newsAttachment = "";
newsID = 0;
emailSent = 0;
emailError = 0;
}
private void ReadText(object sender, FileSystemEventArgs e)
{
ClearVar();
SetLimits();
string txtFile = filePath + @"\QueueEmail.txt";
StreamReader sr = new StreamReader(txtFile);
string txtLine = String.Empty;
try
{
while ((txtLine = sr.ReadLine()) != null)
{
string[] lineCpl = txtLine.Split('§');
newsType = lineCpl[0];
userName = lineCpl[1];
newsID = Convert.ToInt32(lineCpl[2]);
}
}
catch (IOException ex)
{
SendExByMail("ReadText() IO Error", ex);
}
catch (Exception ex)
{
SendExByMail("ReadText() General Error", ex);
}
finally
{
sr.Close();
sr.Dispose();
}
GetNews();
}
[DllImport("kernel32.dll")]
public static extern bool SetProcessWorkingSetSize(IntPtr proc, int min, int max);
private void SetLimits()
{
GC.Collect();
GC.WaitForPendingFinalizers();
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
SetProcessWorkingSetSize(Process.GetCurrentProcess().Handle, -1, -1);
}
private void DeleteText()
{
try
{
File.Delete(filePath + @"\QueueEmail.txt");
}
catch (IOException ex)
{
SendExByMail("DeleteText() IO Error", ex);
}
catch (Exception ex)
{
SendExByMail("DeleteText() General Error", ex);
}
}
private void GetNews()
{
string connectionString = ConfigurationManager.ConnectionStrings["contacts"].ConnectionString;
SqlConnection conn = new SqlConnection(connectionString);
string sqlSELECT = "SELECT newsSubject, newsContents, username, attachment FROM newsArchive " +
"WHERE ID = " + newsID;
SqlCommand comm = new SqlCommand(sqlSELECT, conn);
try
{
conn.Open();
using (SqlDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
newsSubject = reader[0].ToString();
newsContent = reader[1].ToString();
userName = reader[2].ToString();
newsAttachment = reader[3].ToString();
}
reader.Dispose();
}
}
catch (SqlException ex)
{
SendExByMail("GetNews() SQL Error", ex);
}
catch (Exception ex)
{
SendExByMail("GetNews() General Error", ex);
}
finally
{
comm.Dispose();
conn.Dispose();
}
DeleteText();
GetAddress();
}
private void GetAddress()
{
string connectionString = ConfigurationManager.ConnectionStrings["contacts"].ConnectionString;
SqlConnection conn = new SqlConnection(connectionString);
string sqlSELECT = String.Empty;
if (newsType == "custom")
sqlSELECT = "SELECT DISTINCT email FROM custom";
else
sqlSELECT = "SELECT DISTINCT email FROM contactsMain WHERE queued = 'True'";
SqlCommand comm = new SqlCommand(sqlSELECT, conn);
try
{
conn.Open();
using (SqlDataReader reader = comm.ExecuteReader())
{
while (reader.Read())
{
try
{
if (CheckEmail(reader[0].ToString()) == true)
{
SendNews(reader[0].ToString());
Thread.Sleep(4000);
emailSent++;
}
else
{
SendInvalid(reader[0].ToString());
emailError++;
}
}
catch (SmtpException ex)
{
SendExByMail("NewsLetter Smtp Error", reader[0].ToString(), ex);
emailError++;
}
catch (Exception ex)
{
SendExByMail("Send NewsLetter General Error", reader[0].ToString(), ex);
emailError++;
}
finally
{
UnqueueEmail(reader[0].ToString());
}
}
reader.Dispose();
}
}
catch (SqlException ex)
{
SendExByMail("GetAddress() SQL Error", ex);
}
catch (Exception ex)
{
SendExByMail("GetAddress() General Error", ex);
}
finally
{
comm.Dispose();
conn.Dispose();
}
SendConfirmation();
}
private bool CheckEmail(string emailAddy)
{
bool returnValue = false;
string regExpress = @"^[\w-]+(?:\.[\w-]+)*@(?:[\w-]+\.)+[a-zA-Z]{2,7}$";
Match verifyE = Regex.Match(emailAddy, regExpress);
if (verifyE.Success)
returnValue = true;
return returnValue;
}
private void SendNews(string emailAddy)
{
string today = DateTime.Today.ToString("MMMM d, yyyy");
using (MailMessage message = new MailMessage())
{
SmtpClient smtpClient = new SmtpClient();
MailAddress fromAddress = new MailAddress("");
message.From = fromAddress;
message.To.Add(emailAddy);
message.Subject = newsSubject;
if (newsAttachment != "")
{
Attachment wusaAttach = new Attachment(attachPath + newsAttachment);
message.Attachments.Add(wusaAttach);
}
message.IsBodyHtml = true;
#region Message Body
message.Body = "";
#endregion
smtpClient.DeliveryMethod = SmtpDeliveryMethod.Network;
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
}
private void UnqueueEmail(string emailAddy)
{
string connectionString = ConfigurationManager.ConnectionStrings["contacts"].ConnectionString;
SqlConnection conn = new SqlConnection(connectionString);
string sqlStatement = String.Empty;
if (newsType == "custom")
sqlStatement = "UPDATE custom SET queued = 'False' WHERE email LIKE '" + emailAddy + "'";
else
sqlStatement = "UPDATE contactsMain SET queued = 'False' WHERE email LIKE '" + emailAddy + "'";
SqlCommand comm = new SqlCommand(sqlStatement, conn);
try
{
conn.Open();
comm.ExecuteNonQuery();
}
catch (SqlException ex)
{
SendExByMail("UnqueueEmail() SQL Error", ex);
}
catch (Exception ex)
{
SendExByMail("UnqueueEmail() General Error", ex);
}
finally
{
comm.Dispose();
conn.Dispose();
}
}
private void SendConfirmation()
{
SmtpClient smtpClient = new SmtpClient();
using (MailMessage message = new MailMessage())
{
MailAddress fromAddress = new MailAddress("");
MailAddress toAddress = new MailAddress();
message.From = fromAddress;
message.To.Add(toAddress);
//message.CC.Add(ccAddress);
message.Subject = "Your Newsletter Mailing Has Completed";
message.IsBodyHtml = true;
message.Body = "Total Emails Sent: " + emailSent +
"<br />Total Email Errors: " + emailError +
"<br />Contact regarding email errors if any were found";
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
ClearVar();
System.GC.Collect();
}
private void SendInvalid(string emailAddy)
{
SmtpClient smtpClient = new SmtpClient();
using (MailMessage message = new MailMessage())
{
MailAddress fromAddress = new MailAddress("");
MailAddress toAddress = new MailAddress("");
message.From = fromAddress;
message.To.Add(toAddress);
//message.CC.Add(ccAddress);
message.Subject = "Invalid Email Address";
message.IsBodyHtml = true;
message.Body = "An invalid email address has been found, please check the following " +
"email address:<br />" + emailAddy;
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
}
private void SendExByMail(string subject, Exception ex)
{
SmtpClient smtpClient = new SmtpClient();
using (MailMessage message = new MailMessage())
{
MailAddress fromAddress = new MailAddress("");
MailAddress toAddress = new MailAddress("");
message.From = fromAddress;
message.To.Add(toAddress);
//message.CC.Add(ccAddress);
message.Subject = subject;
message.IsBodyHtml = true;
message.Body = "An Error Has Occurred: <br />Exception: <br />" + ex.ToString();
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
}
private void SendExByMail(string subject, string body, Exception ex)
{
SmtpClient smtpClient = new SmtpClient();
using (MailMessage message = new MailMessage())
{
MailAddress fromAddress = new MailAddress("", "MailerService");
MailAddress toAddress = new MailAddress("");
message.From = fromAddress;
message.To.Add(toAddress);
//message.CC.Add(ccAddress);
message.Subject = subject;
message.IsBodyHtml = true;
message.Body = "An Error Has Occurred:<br /><br />" + body + "<br /><br />Exception: <br />" + ex.ToString();
smtpClient.Host = "";
smtpClient.Credentials = new System.Net.NetworkCredential("");
smtpClient.Send(message);
smtpClient.ServicePoint.CloseConnectionGroup(smtpClient.ServicePoint.ConnectionName);
}
}
}
}
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(6)
System.Net.Mail.Attachment
实现了IDisposable
,因此我会对其调用Dispose()
(使用using()< /code>) 更新:在 Reflector 中打开 MailMessage.Dispose() 确实会对任何附件调用 Dispose。
此外,调用 GC.Collect() 实际上可能会导致大对象堆碎片。让框架负责垃圾收集。
您是否尝试过下载MemProfiler? (他们有试用版。通常在使用后几分钟内就能收回成本!)
System.Net.Mail.Attachment
implementsIDisposable
, so I would callDispose()
on it (use ausing()
) UPDATE: Opening MailMessage.Dispose() up in Reflector DOES call Dispose on any attachments.Also, calling
GC.Collect()
can actually lead to fragmentation of the large object heap. Let the framework take care of garbage collection.Have you tried downloading MemProfiler? (They have a trial version. It usually pays for itself within a few minutes of use!)
由于需要观察的代码量相当大,因此我使用的方法是注释一些块(一次一个),然后再次运行并观察内存图。例如,您可以评论创建电子邮件附件的部分,或者仅评论消息的实际发送。这可能是识别内存去向的最快方法。
希望有帮助。
吕克
Since this is a fairly large amount of code to watch at, the approach I would use is to comment some of the blocks (one at a time), than run again and watch the memory graph. For example, you could comment the part in which you create an attachment to the email, or just comment the actual sending of the message. This probably would be the fastest way to identify where the memory goes.
Hope that helps.
Luc
这里有几个链接可以帮助您开始使用 Windbg 和 !gcroot 来检测实际的内存泄漏。这些指令看起来丑陋而痛苦,而且可能很乏味,但要坚持下去——如果您有内存泄漏,gcroot 可以帮助您找到它们。
http://blogs.msdn.com/alikl/archive/2009/02/15/identifying-memory-leak-with-process-explorer-and-windbg.aspx
http://blogs.msdn.com/delay/archive/2009/03/11/where-s-your-leak-at-using-windbg-sos-and-gcroot-to-diagnose -a-net-memory-leak.aspx
商用分析器可能更容易使用,但我没有使用它们的经验。为了供将来参考和读者使用,这里有一组该主题的搜索词:
希望有帮助。
Here are a couple links to get you started using windbg and !gcroot to detect actual memory leaks. The instructions look ugly and painful, and it can be tedious, but tough it out--if you have memory leaks !gcroot can help you find them.
http://blogs.msdn.com/alikl/archive/2009/02/15/identifying-memory-leak-with-process-explorer-and-windbg.aspx
http://blogs.msdn.com/delay/archive/2009/03/11/where-s-your-leak-at-using-windbg-sos-and-gcroot-to-diagnose-a-net-memory-leak.aspx
A commercially-available profiler may be easier to use, but I don't have experience with them. For future reference and readers, here's a set of search terms for the topic:
Hope that helps.
性能分析是一件困难的事情。您基本上是在收集经验数据并在没有适当控制的情况下推断操作行为。
所以,首先,可能没有问题。尽管 GarbageCollector [GC] 算法是一个黑匣子,但根据我的经验,我已经看到了特定于进程的自适应行为。例如,我注意到 GC 可能需要一天的时间来分析服务的内存使用情况并确定合适的垃圾收集策略。
此外,内存使用量似乎处于“稳定状态”,这表明您的泄漏不是无限制的,并且可能意味着它正在按设计运行。
话虽如此,您可能仍然存在内存问题。也许是泄漏,或者只是内存使用效率低下。运行分析器并尝试按类型缩小内存消耗范围。
在与您类似的场景中,我发现我们的应用程序动态生成数千个内联字符串文字[思考日志语句],导致第一代和第二代垃圾堆膨胀。它们会及时被收集,但这会给系统带来负担。如果您使用大量内联字符串文字,请考虑使用
public const string
或public static readonly string
代替它们。使用const
或static readonly
将在应用程序的生命周期内仅创建该文字的一个实例。解决此问题后,我们发现第三方电子邮件客户端存在真正的内存泄漏。虽然我们的自定义代码在所有情况下都会打开和关闭电子邮件客户端,但电子邮件客户端保留了资源。我不记得这些是 COM 资源[需要显式处置],还是只是一个实施不佳的电子邮件客户端,但解决方案是显式调用
Dispose
。吸取的教训是不要依赖其他人正确实现Dispose模式,而是在可能的情况下显式调用Dispose
。希望这有帮助,
Performance profiling is a difficult thing to do. You are basically collecting empirical data and inferring an operational behaviour without a proper control.
So, first and foremost, there may not be a problem. Although GarbageCollector [GC] algorithms are a black box, in my experience I have seen process-specific adaptive behaviour. For instance I have noted the GC may take up to a day to analyze the service's memory usage and determine a suitable strategy for garbage collection.
Also, that memory usage appears to "plateau" would indicate that your leak is not unbounded, and may mean that it is operating as designed.
Having said that, you may still have memory issues. Perhaps a leak, or perhaps just inefficient memory usage. Run a profiler and try to narrow down memory consumption by type.
In a scenario similar to yours, I found our application generating thousands of inline string literals [think log statements] on the fly, bloating first and second gen garbage heaps. They would in time be collected, but it was taxing on the system. If you use a lot of inline string literals, consider using
public const string
orpublic static readonly string
in their place. Usingconst
orstatic readonly
will create only one instance of that literal for the application's life.After addressing this issue, we found a genuine memory leak around our third party email client. While our custom code would open and close the email client in all circumstances, the email client retained resources. I do not recall if these were COM resources [which would require explicitly disposal], or simply a poorly implemented email client, but the solution was to invoke
Dispose
explicitly. The lesson learned is not to rely on other people to implement the Dispose pattern correctly, but to invokeDispose
explicitly when possible.Hope this helps,
我认为,您不应该在阅读器打开的情况下发送电子邮件。我认为你应该尝试使事情尽可能地解耦,因为代码更容易维护,也更容易阅读。等待 4 秒再次打开连接对我来说似乎有点不自然,您应该始终获取所有数据,然后关闭连接。如果从数据库中获取的数据太大,您可以轻松实现分页机制,例如一次获取 100 封电子邮件。发送完这些后,获取下一个 100 个,等等。
除非我真的别无选择,否则我不会碰 GC,99% 这个工作属于 .Net Framework,所以大多数时候它对程序员来说应该是透明的。
吕克
In my opinion, you should not send the emails with the reader open. I think you should try to keep things as decoupled as possible, since the code is more easier to maintain, and more easy to read. Waiting 4 secs with the connection open again seems a little unnatural to me, you should always get all of your data and then close your connections. If the data risen from the database is too large, you could easy implement a paging mechanism, to get, let's say 100 emails at a time. After sending those, get the next 100, etc.
I would not touch the GC unless I really had no choise, in 99% this job belongs to the .Net Framework, so it should be transparent to programmers most of the time.
Luc
我怀疑这是你的问题,但这给了我一种不好的感觉:
我绝对会在这里使用嵌套的
using
语句。因为using
语句是try/finally
块的语法糖,而嵌套using
语句是嵌套的语法糖>
try/finally
块,这不是这里发生的事情。我怀疑comm.Dispose()
是否抛出异常,但如果抛出异常,conn.Dispose()
将永远不会被调用。另外:是否有理由在
UnqueueEmail
中创建新的SqlConnection
对象,而不是从调用它的方法中传递它?再说一遍,这可能不是问题的根源。尽管如此,在您遇到的情况下,我要做的第一件事就是创建此服务的构建版本,并注释掉所有 SMTP 代码,并在运行时观察其内存使用情况。这是确定问题出在数据库还是邮件程序代码上的相当快的方法。如果这使问题消失,我要做的下一件事是实现一个模拟
SmtpClient
类,其中包含服务正在调用的所有方法的存根版本,然后再次测试;这将告诉您问题是在SmtpClient
类本身内部还是在为其构建数据的代码中。完成此操作大约需要一个小时,您将获得有关您的问题的重要数据,而您目前还没有这些数据。编辑
通过“具有存根方法的模拟
SmtpClient
类”,我的意思是这样的:等等。然后修改您的程序以创建
MockSmtpClient
实例而不是SmtpClient
。由于您的程序似乎没有查看
SmtpClient
的任何属性,或检查任何函数的返回值,或处理任何事件,因此它的行为应该与实现此之前的行为相同- 只是它不会发送任何邮件。如果仍然存在内存问题,那么您已经排除了SmtpClient
这个可能的原因。I doubt that this is your problem, but it gives me a bad feeling:
I would absolutely use nested
using
statements instead here. Because while ausing
statement is syntactic sugar for atry/finally
block, nestedusing
statements are syntactic sugar for nestedtry/finally
blocks, and that's not what's going on here. I doubt thatcomm.Dispose()
is throwing an exception, but if it did,conn.Dispose()
would never get called.Also: is there a reason you're creating a new
SqlConnection
object inUnqueueEmail
, instead of passing it in from methods that call it? Again, it's probably not the source of your problem.All that said, the first thing I'd do in your situation is create a build of this service with all the SMTP code commented out and watch its memory usage as I run it. That's a pretty fast way to determine if the problem is with the database or with the mailer code. If that made the problem go away, the next thing I'd do is implement a mock
SmtpClient
class with stubbed-out versions of all the methods the service is calling and test again; that will tell you if the problem is inside theSmtpClient
class itself or in the code that builds the data for it. It'll take an hour or so to do this, and you'll get important data about your problem that you don't presently have.Edit
By "a mock
SmtpClient
class with stubbed-out methods," I mean something like this:and so on. Then revise your program to create instances of
MockSmtpClient
instead ofSmtpClient
.Since your program doesn't seem to look at any of the properties of
SmtpClient
, or examine the return values of any functions, or handle any events, it should behave the same way it did before you implemented this - only it won't send any mail. If it still has memory problems, then you've eliminatedSmtpClient
as a possible cause.