ArcEngine 中IFeatureLayer、IFeatureClass跨线程访问错误

文章讨论了在ArcEngine二次开发中,如何避免因IFeatureLayer对象的线程限制导致的内存访问错误和地图投影问题。作者提出了在UI线程中强制执行对象创建和操作的解决方案,以确保正确处理跨线程通信和数据映射。
摘要由CSDN通过智能技术生成

结论: IFeatureLayer不可跨线程访问,触发错误:访问受保护或已损坏的内存;

            IFeatureClass可开跨线程访问,但是出现了细节上的错误,并没有引发显示错误。

        在处理基于ArcEngine二次开发的网络TCP通信时,当涉及异步处理,Object对象的创建和调用不在同一线程时引发的种种问题。

          创建Object的函数,该函数处理通信中接收到的PDU(Protocol Data Unit),目标将其转换为AcrEngine中的IFeatureLayer对象后,传递给Main Form,然后Add到窗体的acrMapControl中。

        /// <summary>
        /// When received pdu from client
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="pdu"></param>
        private void OnPDUReceived(Socket socket, PDU pdu)
        {
            Console.Write("\nReceived data from Server!\n");
            if (pdu._Type == InformationType.DataSend)
            {
                IFeatureLayer featureLayer = Protocol.Feature.ToMapLayer(pdu._Features);
                
                // Induce Event in Form 
                OnPDUReceivedEvent(featureLayer);
            }
            else
                throw new Exception("Unexpected information type send form server!");
             
         }

        该Handle接收信息的函数在异步接收函数中被调用,故该函数也在异步线程中执行,调用其的函数如下,该函数在异步线程中循环接收通信Socket发来的信息,并在接收到完整的一次信息后调用OnPDUReceived函数:

 /// <summary>
        /// Async Receive data from server
        /// </summary>
        /// <param name="socket"></param>
        public void Receive()
        {
            Task.Run(() =>
            {
                try
                { 
                    while (true)
                    {
                        // 1) Receive the head part, the bytes count of the body part
                       byte[] head = new byte[4];
                        if(_Socket.Connected)
                            _Socket.Receive(head, 0, 4, SocketFlags.None); // Synchronouly
                       
                        int size = BitConverter.ToInt32(head, 0);
                        
                        // 2) Receive the body part of the stream and deserialize it to PDU
                        byte[] body = new byte[size];
                        _Socket.Receive(body, 0, size, SocketFlags.None);
                        PDU pdu = PDU.Deserialize(body);

                        // 3) Handle received pdu
                        OnPDUReceived(_Socket, pdu);

                    }
                }
                catch (SocketException ex)
                {
                    // Handle socket exception, e.g., connection closed
                    throw new Exception(ex.Message);

                }
            });
        }

        OnPDUReceived()引发Main Form中Event: OnPDUReceivedEvent()

      void _TcpClient_OnPDUReceivedEvent(IFeatureLayer featureLayer)
        {     
            axMapControl1.Invoke((MethodInvoker)delegate
            {
                
                Console.WriteLine("Add Point Layer");
                axMapControl1.AddLayer(featureLayer);
                axMapControl1.Refresh();  
               
            });
        }

        显然,该事件通过Control.Invoke()强制在UI线程中运行,故重新回到第一个OnPDUReceived()函数中,主窗体中的两条语句在两个不同线程中运行,第一条语句负责创建IFeatureLayer,第二条语句则调用IFeatureLayer对象用于更新MapControl,当按上述代码运行时,会引发如下错误:

        IFeatureLayer对象不允许跨线程访问,其实Shp文件当被打开时会生成lock文件,该lock临时文件锁定了对象

        既然IFeatureLayer对象不允许跨线程访问,尝试对IFeatureClass对象进行跨线程访问,也就是在OnPDUReceivedEvent中传递IFeatureClass对象,IFeatureClass对象为IFeatureLayer的唯一实质性成员,故可以在Event中转为Layer实现

 /// <summary>
        /// Event: When Received data: Update the Map Layer
        /// </summary>
        /// <param name="featureClass"></param>
        void _TcpClient_OnPDUReceivedEvent(IFeatureClass featureClass)
        {
            
            axMapControl1.Invoke((MethodInvoker)delegate
            {
                // Create Layer form class
                IFeatureLayer featureLayer = new FeatureLayerClass();
                featureLayer.FeatureClass = featureClass;
                featureLayer.Name = "Null";

                Console.WriteLine("Add Point Layer");
                axMapControl1.AddLayer(featureLayer);
                axMapControl1.Refresh();
                
               
            });
        }

        对转换类函数Feature.ToMapLayer也做对应更改,运行发现没有中断程序,但是出现了不能投影的问题,无论如何设置ISpatialReference,无论是对class,layer还是Control.map对象设置都无济于事,显示中始终按照原本的坐标系显示,或者说,按照赋予每条要素的X,Y坐标显示;后续发现,倘若在Event中不引用任何参数,直接打开保存到指定目录的shp文件,则可成功显示,实际也未设置该layer的投影,但程序运行显示时则已经进行了投影(mapControl还有一个mapServerLayer,该featureLayer自动按照其投影坐标系进行投影)。至于该问题的原因,推测可能是因为类跨线程访问权限不足导致的。

解决方案:将类的创建也强制execute于UI线程下:

  /// <summary>
        /// When received pdu from client
        /// </summary>
        /// <param name="socket"></param>
        /// <param name="pdu"></param>
        private void OnPDUReceived(Socket socket, PDU pdu)
        {
            Console.Write("\nReceived data from Server!\n");
            if (pdu._Type == InformationType.DataSend)
            {
                // Force Execute in UI thread
                if (Application.OpenForms.Count > 0)
                {
                    Form mainForm = Application.OpenForms[0];
                    if (mainForm != null && !mainForm.IsDisposed)
                    {
                        mainForm.Invoke((MethodInvoker)delegate
                        {
                            // Serious Note:: this must be execute whthin same thread, another unexpected and unexplained error would occur!;

                            IFeatureLayer featureLayer = Protocol.Feature.ToMapLayer(pdu._Features);
                             // Induce Event in Form
                            OnPDUReceivedEvent(featureLayer);
                        });
                    }
                }
            }
            else
                throw new Exception("Unexpected information type send form server!");
             
         }

  • 9
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ArcEngine使用Select Elements选择元素的步骤如下: 1. 获取MapControl或PageLayoutControl对象。 2. 创建一个新的SelectionEnvironment对象,并设置选择条件、符号等属性。 3. 调用MapControl或PageLayoutControl的ClearSelection方法,清空当前选择集。 4. 调用MapControl或PageLayoutControl的SelectByShape或SelectByPoint等方法,根据指定的几何图形或位置选择元素。 5. 遍历MapControl或PageLayoutControl的Selection集合,获取所选元素的ID号。 6. 根据ID号获取元素对象,执行相应操作。 以下是一个示例代码: ```C# // 获取MapControl对象 ESRI.ArcGIS.Controls.AxMapControl mapControl = this.axMapControl1; // 创建SelectionEnvironment对象 ESRI.ArcGIS.Carto.ISelectionEnvironment selectionEnv = new ESRI.ArcGIS.Carto.SelectionEnvironmentClass(); selectionEnv.CombinationMethod = ESRI.ArcGIS.Carto.esriSelectionResultEnum.esriSelectionResultNew; selectionEnv.SelectionColor = GetRGBColor(255, 0, 0); // 设置选择符号颜色 // 清空当前选择集 mapControl.Map.ClearSelection(); // 创建选择几何图形并进行选择 ESRI.ArcGIS.Geometry.IPoint point = new ESRI.ArcGIS.Geometry.PointClass(); point.PutCoords(x, y); // 设置点的坐标 ESRI.ArcGIS.Display.IScreenDisplay screenDisplay = mapControl.ActiveView.ScreenDisplay; double tolerance = screenDisplay.DisplayTransformation.FromPoints(5); // 设置选择容差 ESRI.ArcGIS.Display.IDisplayFeedback displayFeedback = new ESRI.ArcGIS.Display.RubberBandFeedbackClass(); displayFeedback.Display = screenDisplay; ESRI.ArcGIS.Geometry.IGeometry geometry = displayFeedback.TrackPoint(); // 获取选择几何图形 mapControl.Map.SelectByShape(geometry, selectionEnv, false); // 根据几何图形进行选择 // 遍历选择集合并获取元素对象 ESRI.ArcGIS.Carto.IEnumFeature enumFeature = (ESRI.ArcGIS.Carto.IEnumFeature)mapControl.Map.FeatureSelection; ESRI.ArcGIS.Geodatabase.IFeature feature; enumFeature.Reset(); while ((feature = enumFeature.Next()) != null) { // 根据ID号获取元素对象并执行操作 int featureID = feature.OID; ESRI.ArcGIS.Carto.IFeatureLayer featureLayer = (ESRI.ArcGIS.Carto.IFeatureLayer)mapControl.Map.get_Layer(0); ESRI.ArcGIS.Carto.IFeatureSelection featureSelection = (ESRI.ArcGIS.Carto.IFeatureSelection)featureLayer; ESRI.ArcGIS.Geodatabase.IQueryFilter queryFilter = new ESRI.ArcGIS.Geodatabase.QueryFilterClass(); queryFilter.WhereClause = "OBJECTID = " + featureID; ESRI.ArcGIS.Geodatabase.IFeatureCursor featureCursor = featureLayer.Search(queryFilter, false); ESRI.ArcGIS.Geodatabase.IFeature feature2 = featureCursor.NextFeature(); // 执行操作 // ... } ``` 在上述代码,首先获取MapControl对象,并创建SelectionEnvironment对象,设置选择条件。然后调用MapControl的ClearSelection方法,清空当前选择集。接着创建选择几何图形,使用MapControl的SelectByShape方法进行选择。最后遍历选择集合,根据ID号获取元素对象,并执行相应操作。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值