结论: 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!");
}