ArcEngine多线程开发

  一、前言

  GIS应用开发中,会遇到计算量大耗时长的操作,如果使用单线程开发则UI界面会卡死,这种情况是令人抓狂的。为了实现执行某操作时UI界面保持响应,我们可以使用多线程开发。阅读这篇文章之前需要先了解同步和异步、多线程、STA和MTA、委托(也有资料翻译为“代理”)等相关内容。

  二、AE多线程开发的主要障碍

  AO对象是STA对象,无法在线程间相互传递/共享(什么是STA对象可自行百度,这篇博客中举的例子很好)。同时,AE开发时线程会被标记为STA线程,STA线程之间相互传递的对象必须是简单类型或托管类型。针对此问题,我们可以将AO对象序列化为字符串,将序列化得到的字符串在STA进程间相互传递,在使用时将序列化字符串反序列化为AO对象(AO对象序列化和反序列化可以查看这篇博客),然后执行相关操作。

  三、多线程开发示例

  以坡度计算为例,主要使用的接口和类包括:图层数据相关(IRasterLayer、IGeoDataset、IName、IRasterDataset);分析操作相关(ISurfaceOp);线程类(Thread)

  思路:

  1、声明一个委托用于回调操作结果,编写业务操作函数和操作结果回调函数

  2、实例化工作线程,并将工作线程的ApartmentState设置为STA

  3、从MapControl中获取指定名称的栅格图层,将栅格图层数据源Name对象序列化为字符串传递给工作线程执行分析

  4、回调显示操作结果

  1.声明委托

      //回调委托
      private delegate void SlopeResultCallback(string item);

  2.编写业务操作函数和操作结果回调函数

        /// <summary>
        /// 业务函数,用于执行相关操作
        /// </summary>
        private void Slope(object SerStrOfArcObj)
        {
            //反序列化为Name对象
            IName pName = DeSerialzed(SerStrOfArcObj as string) as IName;
            //获取数据源Geodataset
            IGeoDataset pGeoDs = pName.Open() as IGeoDataset;

            //实例化RasterSurfaceOp类
            //可使用IRasterAnalysisEnvironment接口设置分析环境
            //此处只为了演示多线程开发方式,未设置分析环境,读者可根据实际需求自行设置分析环境
            ISurfaceOp pSurfaceOp = new RasterSurfaceOpClass();
            //坡度分析得到分析结果,可使用ISaveAs2接口报错分析结果到指定目录
            IGeoDataset pSlopeRes = pSurfaceOp.Slope(pGeoDs, esriGeoAnalysisSlopeEnum.esriGeoAnalysisSlopeDegrees);
            
            //获取栅格数据集,创建栅格图层
            IRasterDataset pRstDs = (pSlopeRes as IRasterBandCollection).Item(0).RasterDataset;
            IRasterLayer pRstLyr = new RasterLayerClass();
            pRstLyr.CreateFromDataset(pRstDs);

            //将pRstLyr序列化为字符串传递给回调函数,将结果更新的UI界面中(MapControl)显示
            string pRstLyrToStr = Serialzed(pRstLyr);
            AddSlopeResultToMap(pRstLyrToStr);  
        }

        /// <summary>
        /// 回调函数,更新UI
        /// </summary>
        /// <param name="item"></param>
        private void AddSlopeResultToMap(string item)
        {
            if (this.axMapControl1.InvokeRequired)
            {
                SlopeResultCallback d = new SlopeResultCallback(AddSlopeResultToMap);
                this.Invoke(d, new object[] { item });  //切换到UI线程执行委托,this.Invoke()的用法可自行查阅相关资料
            }
            else
            {
                //反序列化
                IRasterLayer plyr = DeSerialzed(item) as IRasterLayer;
                this.axMapControl1.AddLayer(plyr as ILayer);  
            }
        }

  3.实例化工作线程并设置为STA,在工作线程里运行业务操作函数

        /// <summary>
        /// 执行坡度分析
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSlope_Click(object sender, EventArgs e)
        {
            //获取栅格图层,然后获取数据源Name对象
            IRasterLayer pRstLyr = GetLayerByName(axMapControl1, "宋庆国.jpg") as IRasterLayer;
            if (pRstLyr == null)
                return;
            IName pName = (pRstLyr as IDataLayer).DataSourceName;

            //将Name对象序列化为字符串
            string pNameToStr = Serialzed(pName);

            //实例化工作线程,并将工作线程设置为STA模式
            Thread t = new Thread(new ParameterizedThreadStart(Slope));
            t.SetApartmentState(ApartmentState.STA);
            //把序列化得到的字符串传递给工作线程
            t.Start(pNameToStr);
        }

  4.完整代码

using ESRI.ArcGIS.Carto;
using ESRI.ArcGIS.Controls;
using ESRI.ArcGIS.DataSourcesFile;
using ESRI.ArcGIS.DataSourcesRaster;
using ESRI.ArcGIS.esriSystem;
using ESRI.ArcGIS.GeoAnalyst;
using ESRI.ArcGIS.Geodatabase;
using Microsoft.Win32;
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Drawing;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
using System.Windows.Forms;

namespace WindowsFormsApplication1
{  
    public partial class Form1 : Form
    {
        //回调委托
        private delegate void SlopeResultCallback(string item);

        public Form1()
        {
            InitializeComponent();
        }

        /// <summary>
        /// 回调函数,更新UI
        /// </summary>
        /// <param name="item"></param>
        private void AddSlopeResultToMap(string item)
        {
            if (this.axMapControl1.InvokeRequired)
            {
                SlopeResultCallback d = new SlopeResultCallback(AddSlopeResultToMap);
                this.Invoke(d, new object[] { item });  //切换到UI线程执行委托,this.Invoke()的用法可自行查阅相关资料
            }
            else
            {
                //反序列化
                IRasterLayer plyr = DeSerialzed(item) as IRasterLayer;
                this.axMapControl1.AddLayer(plyr as ILayer);  
            }
        }

        /// <summary>
        /// 业务函数,用于执行相关操作
        /// </summary>
        private void Slope(object SerStrOfArcObj)
        {
            //反序列化为Name对象
            IName pName = DeSerialzed(SerStrOfArcObj as string) as IName;
            //获取数据源Geodataset
            IGeoDataset pGeoDs = pName.Open() as IGeoDataset;

            //实例化RasterSurfaceOp类
            //可使用IRasterAnalysisEnvironment接口设置分析环境
            //此处只为了演示多线程开发方式,未设置分析环境,读者可根据实际需求自行设置分析环境
            ISurfaceOp pSurfaceOp = new RasterSurfaceOpClass();
            //坡度分析得到分析结果,可使用ISaveAs2接口报错分析结果到指定目录
            IGeoDataset pSlopeRes = pSurfaceOp.Slope(pGeoDs, esriGeoAnalysisSlopeEnum.esriGeoAnalysisSlopeDegrees);
            
            //获取栅格数据集,创建栅格图层
            IRasterDataset pRstDs = (pSlopeRes as IRasterBandCollection).Item(0).RasterDataset;
            IRasterLayer pRstLyr = new RasterLayerClass();
            pRstLyr.CreateFromDataset(pRstDs);

            //将pRstLyr序列化为字符串传递给回调函数,将结果更新的UI界面中(MapControl)显示
            string pRstLyrToStr = Serialzed(pRstLyr);
            AddSlopeResultToMap(pRstLyrToStr);  
        }

        /// <summary>
        /// 执行坡度分析
        /// </summary>
        /// <param name="sender"></param>
        /// <param name="e"></param>
        private void btnSlope_Click(object sender, EventArgs e)
        {
            //获取栅格图层,然后获取数据源Name对象。axMapControl1是窗体上的地图控件
            IRasterLayer pRstLyr = GetLayerByName(axMapControl1, "宋庆国.jpg") as IRasterLayer;
            if (pRstLyr == null)
                return;
            IName pName = (pRstLyr as IDataLayer).DataSourceName;

            //将Name对象序列化为字符串
            string pNameToStr = Serialzed(pName);

            //实例化工作线程,并将工作线程设置为STA模式
            Thread t = new Thread(new ParameterizedThreadStart(Slope));
            t.SetApartmentState(ApartmentState.STA);
            //把序列化得到的字符串传递给工作线程
            t.Start(pNameToStr);
        }

        /// <summary>
        /// 根据名字获取图层
        /// </summary>
        /// <param name="axMapControl1"></param>
        /// <param name="lyrName"></param>
        /// <returns></returns>
        private ILayer GetLayerByName(AxMapControl axMapControl1, string lyrName)
        {
            for (int i = 0; i < axMapControl1.LayerCount; i++)
            {
                ILayer tempLyr = axMapControl1.get_Layer(i);
                if (tempLyr.Name == lyrName)
                    return tempLyr;
            }
            return null;
        }

        /// <summary>
        /// 序列化实现IPersistStream接口的对象
        /// </summary>
        /// <param name="obj"></param>
        /// <returns></returns>
        private string Serialzed(object obj)
        {
            string serialzedAsXMLString = null;
            try
            {
                ///序列化
                IXMLPersistedObject xmlWrapper = new XMLPersistedObjectClass();
                xmlWrapper.Object = obj;
                IXMLSerializer xmlSerializer = new XMLSerializerClass();
                serialzedAsXMLString = xmlSerializer.SaveToString(xmlWrapper, null, null);              
            }
            catch { }
            return serialzedAsXMLString;
        }

        /// <summary>
        /// 反序列化实现IPersistStream接口的对象
        /// </summary>
        /// <param name="serialzedStr"></param>
        /// <returns></returns>
        private object DeSerialzed(string serialzedStr)
        {
            object obj=null;
            try
            {
                ///反序列化
                IXMLSerializer xmlSerializer = new XMLSerializerClass();
                IXMLPersistedObject deWrapper = xmlSerializer.LoadFromString(serialzedStr, null, null) as IXMLPersistedObject;
                obj = deWrapper.Object;
            }
            catch { }
            return obj;
        }
    }
}

 注:参考AO帮助中的“Writing multithreaded ArcObjects code”

原文地址:https://www.cnblogs.com/songqingguo/p/12820152.html