2005-12-27

λ [.NET] CDO for Windows2000 でメールを送信する 続編

以前のソース の場合、 ASP.NET 2.0 の開発サーバーで使っていると、どうもGC.Collectでのガーベージコレクトが実行されない=メール送信されなくて悩む。 …というか前回の奴もそもそも「2通目の送信処理のタイミングで1通目が実際に送信されている」ようで謎だったのだが、 今まではコンソールアプリで使っていたのであんまり問題になっていなかった。

GC対象の変数を独立した中括弧でくくって変数スコープ外でGCを呼んでみても、 内部的には変数が確保されたままでGCには失敗。 GCされる変数を持つ独立したメソッドを定義して実行してやったらうまくいった。 (コメントに書いた通り、スタックフレームから消去されてGCが確実に回収するものと推測される)

途中の試行錯誤の結果だが、ついでに世代番号も指定するようにしてみた。

using System;
using System.Collections.Generic;
using System.Text;

namespace HogeAppLib
{
    class MTA
    {
        /// <summary>
        /// メールを送信する。SMTPサーバ名は "smtpserver" 固定
        /// </summary>
        /// <param name="mailfrom"></param>
        /// <param name="mailto"></param>
        /// <param name="mailsubj"></param>
        /// <param name="mailbody"></param>
        public static void SendMail(string mailfrom, string mailto, string mailsubj, string mailbody)
        {
            // スタックフレーム上のローカル変数を捨ててからGCを起動したいので
            // 別サブルーチンとして定義した
            int gcGeneration = SendMailSub(mailfrom, mailto, mailsubj, mailbody);
            try
            {
                // 実際にメール送信するのは、iMsg が消滅する直前。
                // GCを行うとすぐにメール送信できる
                GC.Collect(gcGeneration);
            }
            catch (Exception e)
            {
                システムLogger.Fatal(e.Message);
            }
            return;
        }

        public static int SendMailSub(string mailfrom, string mailto, string mailsubj, string mailbody)
        {
            システムLogger.Info(String.Format("sendmail to: {0}", mailto));
            int gcGeneration = 0;
            try
            {
                CDO.MessageClass iMsg = new CDO.MessageClass();
                CDO.ConfigurationClass iConf = new CDO.ConfigurationClass();
                string smtpserver = "smtpserver"; // ホスト名「smtpserver」をDNSまたは hosts ファイルに定義する
                gcGeneration = GC.GetGeneration(iMsg);

                iConf.Fields["http://schemas.microsoft.com/cdo/configuration/sendusing"].Value = 2; // cdoSendUsingPort
                iConf.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserver"].Value = smtpserver;
                iConf.Fields["http://schemas.microsoft.com/cdo/configuration/smtpserverport"].Value = 25;
                iConf.Fields["http://schemas.microsoft.com/cdo/configuration/smtpauthenticate"].Value = 0; // 認証なし
                iConf.Fields["http://schemas.microsoft.com/cdo/configuration/smtpusessl"].Value = false; // SSL(TLS) は使わない
                iConf.Fields["http://schemas.microsoft.com/cdo/configuration/smtpconnectiontimeout"].Value = 30; // タイムアウト30秒
                iConf.Fields.Update();

                iMsg.Configuration = iConf;
                iMsg.To = mailto;
                iMsg.From = mailfrom;
                iMsg.Subject = mailsubj;
                iMsg.TextBody = mailbody;
                iMsg.TextBodyPart.ContentTransferEncoding = "7bit";
                iMsg.TextBodyPart.Charset = "ISO-2022-JP";

                iMsg.Send();
            }
            catch (Exception e)
            {
                システムLogger.Fatal(e.Message);
            }
            return gcGeneration;
        }
    }
}

システムLogger は log4net を簡単に使うためのラッパークラス。

また、途中 System.Net.Mail.SmtpClient を使う実験もしていたが、状況はあまり変わらない。 おそらくこいつも明示的にGCをかける必要があると想像される。

本日のツッコミ(全2件) [ツッコミを入れる]
λ hir (2005-12-29 01:50)

CDOがCOMである以上、回収されないリスクは避けがたいような気がします。可能ならManagedなライブラリを使ったほうが。

個人的にはこれを使ってます。aspnetではなくWindowsサービスで使っていますが、きわめて安定です。
http://www.lesnikowski.com/Projects/Mail/Index.aspx

別案としてはlog4netのsmtpappenderで送ってしまうとか。

λ 上美谷 (2005-12-31 19:25)

COM自体はリファレンスカウント方式で
ローカル変数である以上は他のスレッド/プロセスが
AddRefするとは思えないので
COMに由来する理由で回収されないリスクはないだろうと踏んでいます。

そもそもGCしないとメールが出ない仕様はわけわからんのですが
System.Net.Mail.SmtpClient が
クラスのネームスペースを移動する手間はかけても
メールがすぐに出ない(=どうも裏で CDO を使っている)
という仕様を変えてない雰囲気で、
何か深い意図があるのかもしれません。

端にこちらには関係ないExchangeServerの存在のせいかもしれませんが…

Lesnikowski ひと段落したら試してみます。

[]