3

これは最近かなり人気のある問題/質問のようですが、問題の解決策が見つからないようです.

メールを送信するための単純な Windows サービスを c# で作成しました。アプリは、メモリ使用量を除いてうまく機能します。アプリのフロント エンドは Web ベースであり、サービスはディレクトリに作成されるテキスト ファイルによってキューに入れられます。テキスト ファイルを読み取った後、サービスは MS SQL データベースからニュースレター情報と電子メール アドレスを収集し、4 秒ごとに 1 つの電子メールの送信を開始します。タスク マネージャーでサービスの実行を見ていると、CPU 使用率が 4 秒ごとに上昇し、すぐに低下することがわかります。一方、メモリはすべてのメールではなく、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);
            }
        }
    }
}
4

6 に答える 6

6

System.Net.Mail.AttachmentUPDATE : リフレクターで MailMessage.Dispose() を開くと、添付ファイルに対して Dispose が呼び出され IDisposableます。Dispose()using()

また、呼び出しGC.Collect()は、実際には大きなオブジェクト ヒープの断片化につながる可能性があります。フレームワークにガベージ コレクションを処理させます。

MemProfilerをダウンロードしてみましたか? (彼らは試用版を持っています。通常、数分で元が取れます!)

于 2010-01-26T15:46:57.090 に答える
0

私の意見では、リーダーを開いた状態でメールを送信するべきではありません。コードは保守しやすく、読みやすいので、できるだけ切り離しておく必要があると思います。接続が再び開いた状態で4秒間待つことは、私には少し不自然に思えます。常にすべてのデータを取得してから、接続を閉じる必要があります。データベースから生成されたデータが大きすぎる場合は、ページングメカニズムを簡単に実装して、たとえば一度に100通の電子メールを取得できます。それらを送信した後、次の100などを取得します。

本当に選択の余地がない限り、GCには触れません。99%では、このジョブは.Net Frameworkに属しているため、ほとんどの場合、プログラマーには透過的である必要があります。

リュック

于 2010-01-27T07:26:27.343 に答える
0

これがあなたの問題だとは思いませんが、気分が悪くなります。

try
{
    conn.Open();
    comm.ExecuteNonQuery();
    ...
}
finally
{
    comm.Dispose();
    conn.Dispose();
}

ここでは、代わりにネストされたusingステートメントを絶対に使用します。usingステートメントはtry/finallyブロックの構文糖衣ですが、ネストされたステートメントusingネスト try/finallyされたブロックの構文糖衣であり、ここではそうではありません。それが例外をスローしているとは思えませんcomm.Dispose()が、例外がスローされた場合、conn.Dispose()呼び出されることはありません。

また、それを呼び出すメソッドから渡すのではなく、で新しいSqlConnectionオブジェクトを作成する理由はありますか? UnqueueEmail繰り返しますが、それはおそらく問題の原因ではありません。

そうは言っても、あなたの状況で私が最初に行うことは、すべてのSMTPコードをコメントアウトしてこのサービスのビルドを作成し、実行中にメモリ使用量を監視することです. これは、問題がデータベースにあるのかメーラー コードにあるのかを判断する非常に迅速な方法です。これで問題が解決した場合、次に行うことはSmtpClient、サービスが呼び出しているすべてのメソッドのスタブ化されたバージョンを使用してモック クラスを実装し、再度テストすることです。これにより、問題がSmtpClientクラス自体にあるのか、それともデータを構築するコードにあるのかがわかります。これを行うには 1 時間ほどかかりますが、現在持っていない問題に関する重要なデータが得られます。

編集

「スタブ化されたメソッドを備えたモックSmtpClientクラス」とは、次のような意味です。

public class MockSmtpClient()
{
   public string From { get; set; }
   public string To { get; set; }
   public void Send(MailMessage message) { }
}

等々。MockSmtpClient次に、 の代わりに のインスタンスを作成するようにプログラムを修正しますSmtpClient

あなたのプログラムは のプロパティを調べたり、関数の戻り値を調べたり、イベントを処理したりしていないように見えるSmtpClientため、これを実装する前と同じように動作する必要があります。郵便物。それでもメモリの問題がある場合はSmtpClient、考えられる原因を排除しています。

于 2010-01-26T22:10:54.420 に答える
0

パフォーマンス プロファイリングは難しい作業です。基本的に、経験的なデータを収集し、適切な制御なしで運用上の動作を推測しています。

ですので、まず問題ないかもしれません。GarbageCollector [GC] アルゴリズムはブラック ボックスですが、私の経験では、プロセス固有の適応動作を見てきました。たとえば、GC がサービスのメモリ使用量を分析し、ガベージ コレクションの適切な戦略を決定するのに最大 1 日かかる場合があることを指摘しました。

また、メモリ使用量が「横ばい」に見える場合は、リークが無制限ではないことを示しており、設計どおりに動作していることを意味する場合があります。

そうは言っても、まだメモリの問題があるかもしれません。おそらくリークか、単に非効率的なメモリ使用量です。プロファイラーを実行し、メモリ消費量をタイプ別に絞り込んでみます。

あなたと同様のシナリオで、私たちのアプリケーションが何千ものインライン文字列リテラル [ログ ステートメントと考えてください] をオンザフライで生成し、第 1 世代と第 2 世代のガベージ ヒープを肥大化させていることがわかりました。それらはやがて回収されるでしょうが、それはシステムに負担をかけていました。インライン文字列リテラルを多く使用する場合は、代わりにpublic const stringorpublic static readonly stringを使用することを検討してください。constorを使用static readonlyすると、アプリケーションの存続期間中、そのリテラルのインスタンスが 1 つだけ作成されます。

この問題に対処した後、サード パーティの電子メール クライアントで本物のメモリ リークが発見されました。カスタム コードはすべての状況で電子メール クライアントを開いたり閉じたりしますが、電子メール クライアントはリソースを保持していました。Disposeこれらが [明示的な破棄が必要な] COM リソースだったのか、それとも実装が不十分な電子メール クライアントだったのかは覚えていませんが、解決策は明示的に呼び出すことでした。学んだ教訓は、Dispose パターンを正しく実装するために他の人に頼るのではなくDispose、可能な場合は明示的に呼び出すことです。

お役に立てれば、

于 2010-01-26T16:36:09.167 に答える
0

これは監視するコードの量がかなり多いため、再度実行してメモリ グラフを監視するよりも、いくつかのブロックを (一度に 1 つずつ) コメントする方法を使用します。たとえば、メールの添付ファイルを作成する部分にコメントを付けたり、メッセージの実際の送信にコメントを付けたりすることができます。これはおそらく、メモリがどこにあるのかを特定する最も速い方法です。

それが役立つことを願っています。

リュック

于 2010-01-26T16:03:37.027 に答える
0

ここでは、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-リーク.aspx

市販のプロファイラーの方が使いやすいかもしれませんが、私は使った経験がありません。今後の参照と読者のために、トピックの検索用語のセットを次に示します。

find managed memory leaks root

それが役立つことを願っています。

于 2010-01-26T16:21:35.483 に答える