ArcObjectsでマルチスレッド描画


画面二分割方式
粗削りでも速度は出そう、安定化や機能付加等していけばもしかしてと言う感じ。
おおよそ2倍程度の速度向上は魅力だが、データ次第かも (軽いデータは効果薄いかも)。。。
仮想マシンに2プロセス割り当てて確認 (物理2コア/論理4スレッド ホスト)
今更シングルコアマシンも珍しくなりつつあるので、使いどころはあるかも。
4分割とかすれば、多コアではいい感じになるか、とりあえず材料としては面白いか。

しかし強引すぎるコード、まあレイヤ差し替えで戻せる範囲で使う分にはあとどの程度コストかけられるかで判断可能なのでよしか。
(良からぬことが起きるため封印ということになる想定をしておくべき)

こいつがベース
MXDレイヤ

STAでリソース分離というお作法は守ったつもりだが、お作法に従うと七面倒なうえに遅そう。

下表の単位は秒

レイヤ名 Total 速度比
マルチスレッド 0.25213 64.8%
通常地図 0.38935 na
マルチスレッド 1.6496 52.7%
通常地図 3.12911 na
マルチスレッド 3.68957 69.6%
通常地図 5.2995 na
マルチスレッド 2.90795 46.8%
通常地図 6.21826 na
マルチスレッド 2.8289 58.2%
通常地図 4.86284 na
マルチスレッド 1.9524 63.3%
通常地図 3.08223 na

欠点は下記のようにラベルが左右画面でそれぞれ描画されること。
( 都合 DB的には2描画クエリ – 矩形がちいさめだが )
画面は時間計測に使った重いデータではない。
Multi-Thread-Label
アノテーション等背景化をおすすめ
しかし初回読み込みは都合3枚 MXD起動のため遅いだろう。

ちなみにベースマップレイヤはきっちりレイヤそのものの描画時間が取れないが2/3~4/5程度の描画時間
(※描画完了を表す「全てのレイヤ」の合算と合わない)

レイヤ名 Total 速度比
通常レイヤ 3.09748 na
ベースマップ レイヤ 0.1175 na
全てのレイヤ 2.4635 79.53%

コードは下記

using System;
using System.Runtime.InteropServices;
using System.Drawing;
using System.Drawing.Imaging;
using System.Threading;
using System.Diagnostics;

using ESRI.ArcGIS.ADF;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Geometry;

[ComVisible(true)]
[Guid("937568DF-5807-438F-822A-49DB68A1EC8F")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Custom.CustomMxdThreadLayer")]
public class CustomMxdThreadLayer : CustomMxdLayer, IDisposable
{
    private struct Extent
    {
        public double MinX, MinY, MaxX, MaxY;
        public Rectangle Rect;
    }

    private int m_Syn = 0;
    private Extent[] m_ExportOptions = null;
    private ManualResetEvent[] m_RestEvents = new ManualResetEvent[]
    {
        new ManualResetEvent(false),
        new ManualResetEvent(false)
    };
    private ManualResetEvent[] m_RestMainEvents = new ManualResetEvent[]
    {
        new ManualResetEvent(true),
        new ManualResetEvent(true)
    };

    private Bitmap[] m_Imgs = new Bitmap[2];

    private bool m_endLoop = false;

    public CustomMxdThreadLayer() : base()
    {
    }

    public CustomMxdThreadLayer(string mxdPath)
        : base(mxdPath)
    {
    }

    protected override void InitMap()
    {

        //Type t = Type.GetTypeFromCLSID(typeof(AppRefClass).GUID);

        //System.Object obj = Activator.CreateInstance(t);
        //IApplication app = obj as IApplication;

        string mxdPath = base.m_MapDocPath;

        //ベース
        base.InitMap();

        ThreadStart action = () =>
        {
            int threadCount = Interlocked.Increment(ref this.m_Syn);
            threadCount--;
            IEnvelope expEnv = null;
            IMap map = null;

            using (ComReleaser com = new ComReleaser())
            {
                IMapDocument mapdoc = new MapDocumentClass();
                com.ManageLifetime(mapdoc);
                mapdoc.Open(mxdPath, null);
                map = mapdoc.get_Map(0);
                mapdoc.Close();
            }

            IActiveView actView = (IActiveView)map;

            try
            {
                expEnv = new EnvelopeClass();
                while (true)
                {
                    ManualResetEvent mre = this.m_RestEvents[threadCount];

                    mre.WaitOne();
                    mre.Reset(); //即座に非スレッド状態にする
                    //_AppRestMainEvents[threadCount].Reset();//メインスレッドをとめる

                    if (this.m_endLoop)
                        break;

                    if (this.m_ExportOptions == null)
                        continue;

                    expEnv.PutCoords
                    (
                        this.m_ExportOptions[threadCount].MinX,
                        this.m_ExportOptions[threadCount].MinY,
                        this.m_ExportOptions[threadCount].MaxX,
                        this.m_ExportOptions[threadCount].MaxY
                    );
                    Rectangle sysrect = this.m_ExportOptions[threadCount].Rect;
                    tagRECT tag = new tagRECT();
                    tag.left = tag.top = 0;
                    tag.right = sysrect.Width;
                    tag.bottom = sysrect.Height;

                    Bitmap img = new Bitmap(sysrect.Width, sysrect.Height, PixelFormat.Format32bppArgb);
                    this.m_Imgs[threadCount] = img;

                    using (Graphics g = Graphics.FromImage(img))
                    {
                        actView.Output(g.GetHdc().ToInt32(), 96, ref tag, expEnv, null);
                        Debug.Print("Draw:{0}", threadCount);
                    }

                    //メインスレッドを動かす
                    this.m_RestMainEvents[threadCount].Set();

                }//ebd loop
            }
            finally
            {
                if (map != null)
                    Marshal.FinalReleaseComObject(map);

                if (expEnv != null)
                    Marshal.FinalReleaseComObject(expEnv);
            }//end try
        };
        Thread thread1 = new Thread(action);
        thread1.SetApartmentState(ApartmentState.STA);
        // スレッド開始  
        thread1.Start();

        Thread thread2 = new Thread(action);
        thread2.SetApartmentState(ApartmentState.STA);
        // スレッド開始  
        thread2.Start();

    }//end method

    private bool m_isSetDispose = false;

    public override void Draw(esriDrawPhase DrawPhase, IDisplay Display, ITrackCancel TrackCancel)
    {
        if (!this.m_isSetDispose && Display is IScreenDisplay)
        {
            try
            {
                IScreenDisplay sd = (IScreenDisplay)Display;
                System.Windows.Forms.Control ct = System.Windows.Forms.Control.FromHandle((IntPtr)sd.hWnd);
                ct.Disposed += delegate(object o, EventArgs e)
                {
                    this.Dispose();
                };
            }
            catch
            {
            }
            this.m_isSetDispose = true;
            //System. sd.hWnd // WindowProcでとじられたときスレッド止める等した方が無難?
        }

        //Geographyのみ描画
        if (DrawPhase != esriDrawPhase.esriDPGeography)
            return;
        using (ComReleaser com = new ComReleaser())
        {
            IDisplayTransformation diTransform = Display.DisplayTransformation;
            tagRECT rect = diTransform.get_DeviceFrame();

            int scWidth = rect.right;
            int scHeight = rect.bottom;

            IPoint leftU = diTransform.ToMapPoint(0, 0);
            com.ManageLifetime(leftU);

            IPoint rightB = diTransform.ToMapPoint(scWidth, scHeight);
            com.ManageLifetime(rightB);

            IEnvelope env = new EnvelopeClass();
            com.ManageLifetime(env);
            env.PutCoords(leftU.X, leftU.Y, rightB.X, rightB.Y);
            //IEnvelope env = diTransform.Bounds;
            //com.ManageLifetime(env);
            //tagRECT rect = new tagRECT();
            //com.ManageLifetime(rect);

            diTransform.TransformRect(env, ref rect, (int)esriDisplayTransformationEnum.esriTransformToDevice);

            Extent[] exportOptions = new Extent[]
            {
                new Extent() { MinX = env.XMin , MinY = env.YMin , MaxX = env.XMin + (env.Width/2),MaxY = env.YMax ,
                    Rect = new Rectangle( rect.left,rect.top, Convert.ToInt32(rect.right / 2), rect.bottom) },
                new Extent() { MinX = env.XMin + (env.Width/2) , MinY = env.YMin , MaxX = env.XMax ,MaxY = env.YMax ,
                    Rect = new Rectangle( Convert.ToInt32(rect.right / 2), rect.top , rect.right - Convert.ToInt32(rect.right / 2), rect.bottom) }
            };

            this.m_ExportOptions = exportOptions;
            //foreach (var item in _AppRestMainEvents)
            //{
            //    //まだ動いてたら待つ
            //    item.WaitOne();
            //}
            foreach (var item in this.m_RestMainEvents)
            {
                //名スレッドを非シグナル状態に
                item.Reset();
            }
            foreach (var item in this.m_RestEvents)
            {
                //待ちになっている各スレッドを動かす
                item.Set();
            }
            //画像の出来上がりを待つ
            foreach (var item in this.m_RestMainEvents)
            {
                item.WaitOne();
            }

            for (int i = 0; i < this.m_Imgs.Length; i++)
            {
                using (Bitmap img = this.m_Imgs[i])
                using (Graphics gra = Graphics.FromHdc((IntPtr)Display.hDC))
                {
                    gra.DrawImageUnscaled
                    (
                        img,
                        exportOptions[i].Rect.X,
                        exportOptions[i].Rect.Y
                    );
                }//end img
            }//end loop

        }//end com
    }//end method

    public void Dispose()
    {
        this.m_endLoop = true;
        if (this.m_RestEvents != null)
        {
            foreach (var item in this.m_RestEvents)
            {
                //待ちになっている各スレッドを動かし止める
                item.Set();
            }
        }
    }//end method

    ~CustomMxdThreadLayer()
    {
        this.Dispose();
    }
}//end class

上記実現のため書き換えたクラス

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Runtime.InteropServices;
using System.Drawing;
using System.IO;
using System.Drawing.Imaging;

using ESRI.ArcGIS.ADF;
using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.Display;
using ESRI.ArcGIS.Geometry;
using ESRI.ArcGIS.Geodatabase;
using ESRI.ArcGIS.DataSourcesRaster;

[ComVisible(true)]
[Guid("822F48B7-0C1F-43C0-A2B6-839525EC3D8B")]
[ClassInterface(ClassInterfaceType.None)]
[ProgId("Custom.CustomMxdLayer")]
public class CustomMxdLayer : PersistBase, IPersitBaseChild, ILayer
{
    [Persist()]
    public string m_MapDocPath = null;

    //[Persist()]
    public IMap m_Map = null;

    [Persist()]
    private bool m_IsVisble = true;

    public CustomMxdLayer()
    {
    }

    public CustomMxdLayer(string mxdPath)
    {
        this.m_MapDocPath = mxdPath;

        this.InitMap();
    }

    protected virtual void InitMap()
    {
        using (ComReleaser com = new ComReleaser())
        {
            IMapDocument mapDoc = new MapDocumentClass();
            com.ManageLifetime(mapDoc);

            mapDoc.Open(this.m_MapDocPath, null);

            //リテラル 0
            this.m_Map = mapDoc.get_Map(0);

            mapDoc.Close();
        }//end com
    }//end method

    //---------------------------------------------------------------
    #region IPersitBaseChild実c1-30523?
    //---------------------------------------------------------------

    public void LoadCore(IVariantStream stream)
    {
        //c1-30035?みc1-28740?み時マップをc1-30035?み直し
        this.InitMap();
    }

    public void SaveCore(IVariantStream stream)
    { 
    }

    //---------------------------------------------------------------
    #endregion IPersitBaseChild実c1-30523?
    //---------------------------------------------------------------

    //---------------------------------------------------------------
    #region ILayer実c1-30523?
    //---------------------------------------------------------------

    public virtual void Draw(esriDrawPhase DrawPhase, IDisplay Display, ITrackCancel TrackCancel)
    {

        //c1-28552?択と注c1-30184?は無c1-30314?
        if (DrawPhase != esriDrawPhase.esriDPGeography)
            return;
        using (ComReleaser com = new ComReleaser())
        {
            //IEnvelope env = Display.DisplayTransformation.FittedBounds;
            ////Display.DisplayTransformation.VisibleBounds;
            //com.ManageLifetime(env);

            //int scWidth = (int)Display.DisplayTransformation.ToPoints(env.Width);
            //int scHeight = (int)Display.DisplayTransformation.ToPoints(env.Height);

            IDisplayTransformation diTransform = Display.DisplayTransformation;

            tagRECT rect = diTransform.get_DeviceFrame();
            int scWidth = rect.right;
            int scHeight = rect.bottom;

            IPoint leftU = diTransform.ToMapPoint(0, 0);
            com.ManageLifetime(leftU);

            IPoint rightB = diTransform.ToMapPoint(scWidth, scHeight);
            com.ManageLifetime(rightB);

            IEnvelope env = new EnvelopeClass();
            com.ManageLifetime(env);
            env.PutCoords(leftU.X, leftU.Y, rightB.X, rightB.Y);

            using (Bitmap img = this.getImg(env, scWidth, scHeight))
            using (Graphics gra = Graphics.FromHdc((IntPtr)Display.hDC))
            {
                gra.DrawImageUnscaled(img, rect.top, rect.left);
            }

        }//end com
    }//end method

    private Bitmap getImg(IEnvelope env, int width, int height)
    {
        Bitmap bmp = new Bitmap(width, height, PixelFormat.Format32bppArgb);
        using (ComReleaser com = new ComReleaser())
        using (Graphics g = Graphics.FromImage(bmp))
        //using (MemoryStream mem = new MemoryStream())
        {
            IMap map = this.m_Map;

            IActiveView actView = (IActiveView)map;
            tagRECT tag = new tagRECT();
            tag.left = tag.top = 0;
            tag.right = width;
            tag.bottom = height;

            actView.Output(g.GetHdc().ToInt32(), 96, ref tag, env, null);

            ////描画確定と破棄
            //g.Dispose();
            ////確任用
            //bmp.Save(@"c:\test.png", ImageFormat.Png);
        }//end graphics

        return bmp;
    }

    public bool Visible
    {
        get
        {
            return this.m_IsVisble;
        }
        set
        {
            this.m_IsVisble = value;
        }
    }

    public IEnvelope AreaOfInterest
    {
        get { return ((IActiveView)this.m_Map).FullExtent; }
    }
    public ISpatialReference SpatialReference
    {
        set { this.m_Map.SpatialReference = value; }
    }

    public string Name
    {
        get
        {
            return this.m_Map.Name;
        }
        set
        {
            this.m_Map.Name = value;
        }
    }

    public int SupportedDrawPhases
    {
        get { return (int)ESRI.ArcGIS.esriSystem.esriDrawPhase.esriDPGeography; }
    }

    public bool Cached
    {
        get
        {
            return false;
        }
        set
        {
        }
    }

    public double MaximumScale
    {
        get
        {
            return 0;
        }
        set
        {
        }
    }

    public double MinimumScale
    {
        get
        {
            return 0;
        }
        set
        {
        }
    }

    public bool ShowTips
    {
        get
        {
            return false;
        }
        set
        {
        }
    }

    public bool Valid
    {
        get { return true; }
    }

    public string get_TipText(double x, double y, double Tolerance)
    {
        return null;
    }

    //---------------------------------------------------------------
    #endregion ILayer実c1-30523?
    //---------------------------------------------------------------

}
カテゴリー: 開発, 設計 タグ: パーマリンク