ArcObjects编程方法(八):多线程ArcObjects程序开发
ArcObjects线程模型
版本:ArcGIS 10.0 VS2010 C#
所有的ArcObjects组件被标识为单线程单元(STA)。每个线程最多只有一个STA,但一个进程可以有多个STA。当STA收到一个函数调用,它将其传递给它所在的线程。因此,STA中的对象同时只能接受并处理一个函数调用,所有的函数调用都会传递到相同的线程。
ArcObjects组件是线程安全的,可以在多线程环境中进行开发。为了使得ArcObjects应用在多线程环境中能够有效执行,应该注意ArcObjects的线程模式为单元独立模式。所有的ArcObjects对象只能在同一个线程中交互。为了使该模式更好的工作,在ArcGIS 9.X中,单例对象被设计为线程内单例,而不是进程内单例,这样避免了单例的跨线程调用。
多线程方式
将耗时较长的操作置于后台线程
当需要运行耗时较长的操作时,可以将其置于后台线程,以便程序能够进行其他任务的操作,更好的响应用户,比如更新表和要素类的信息。
执行长耗时操作时注意以下几点:
- 线程间无法共享ArcObjects组件,在后台线程中,实例化所有需要的对象。
- 传递给线程的信息应该是简单类型或托管类型。
- 当你必须将ArcObjects组件从主线程传递到工作线程时,必须序列化对象,将序列字符串传递给目标线程,然后再反序列化。例如,可以通过XmlSerializerClass将工作空间的连接属性(IPropertySet)序列化为字符串,将字符串传递给工作线程,再通过反序列化在后台线程创建连接对象,这就可以避免跨线程访问。
- 可以通过进度框显示后台线程处理的进度。
以下列子显示了如何通过后台线程进行数据表的填充:
[C#]
// 创建线程 Thread t = new Thread(new ThreadStart(PopulateLocationsTableProc)); // 将线程标识为单线程单元模式. t.SetApartmentState(ApartmentState.STA); // 启动线程. t.Start(); /// <summary> /// 将MajorCities要素类的信息导入本地表中. /// </summary> private void PopulateLocationsTableProc() { //通过registry获取ArcGIS路径. RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE\ESRI\ArcGIS"); string path = Convert.ToString(key.GetValue("InstallDir")); //打开要素类. 实例化工作空间对象,工作空间对象为线程内单例 //since it is a singleton per thread. IWorkspaceFactory wf = new ShapefileWorkspaceFactoryClass()as IWorkspaceFactory; IWorkspace ws = wf.OpenFromFile(System.IO.Path.Combine(path, @ "DeveloperKit10.0\Samples\Data\USZipCodeData"), 0); IFeatureWorkspace fw = ws as IFeatureWorkspace; IFeatureClass featureClass = fw.OpenFeatureClass(m_sShapefileName); //获取字段索引号. int zipIndex = featureClass.FindField("ZIP"); int nameIndex = featureClass.FindField("NAME"); string cityName; long zip; try { //Iterate through the features and add the information to the table. IFeatureCursor fCursor = null; fCursor = featureClass.Search(null, true); IFeature feature = fCursor.NextFeature(); int index = 0; while (null != feature) { object obj = feature.get_Value(nameIndex); if (obj == null) continue; cityName = Convert.ToString(obj); obj = feature.get_Value(zipIndex); if (obj == null) continue; zip = long.Parse(Convert.ToString(obj)); if (zip <= 0) continue; //Add the current location to the locations table. //m_locations is a DataTable that contains the cities and ZIP Codes. //It is defined in the full code before this excerpt starts. DataRow r = m_locations.Rows.Find(zip); if (null == r) { r = m_locations.NewRow(); r[1] = zip; r[2] = cityName; lock(m_locations) { m_locations.Rows.Add(r); } } feature = fCursor.NextFeature(); index++; } //释放要素游标对象. Marshal.ReleaseComObject(fCursor); } catch (Exception ex) { System.Diagnostics.Trace.WriteLine(ex.Message); } }
实现独立的ArcObjects应用程序
.NET环境下实现独立的ArcObjects应用程序,需要将其标注为STA模式:
[C#]
namespace ConsoleApplication1 { class Program { [STAThread] static void Main(string[] args) { // ... } } }
使用托管线程池和后台线程
线程池中的线程是后台进程。进程池提供系统直接管理的工作进程,可以使得进程的使用更加高效。但进程池进程都是多进程单元结构(MTA),因此不能直接运行ArcObjects(单进程单元格式,STA)。可以通过两种方法解决此问题,一种是创建一个特定的ArcObjects进程作为代理MTA进程的每个调用;另一种方法是实现一个普通的STA进程池,例如STA进程数组。
以下是采用STA线程数组,分别执行每个子任务(sebset):
[C#]
/// <summary> /// Class used to pass the task information to the working thread. /// </summary> public class TaskInfo { ... public TaskInfo(int BandID, ManualResetEvent doneEvent) { m_bandID = BandID; m_doneEvent = doneEvent; } ... } ... public override void OnMouseDown(int Button, int Shift, int X, int Y) { ... // Run the subset thread that will spin off separate subset tasks. //By default, this thread will run as MTA. // This is needed to use WaitHandle.WaitAll(). The call must be made // from MTA. Thread t = new Thread(new ThreadStart(SubsetProc)); t.Start(); } /// <summary> /// Main subset method. /// </summary> private void SubsetProc() { ... //Create a dedicated thread for each band of the input raster. //Create the subset threads. Thread[] threadTask = new Thread[m_intBandCount]; //Each thread will subset a different raster band. //All information required for subsetting the raster band will be //passed to the task by the user-defined TaskInfo class. for (int i = 0; i < m_intBandCount; i++) { TaskInfo ti = new TaskInfo(i, doneEvents[i]); ... // Assign the subsetting thread for the rasterband. threadTask[i] = new Thread(new ParameterizedThreadStart(SubsetRasterBand)); // Note the STA that is required to run ArcObjects. threadTask[i].SetApartmentState(ApartmentState.STA); threadTask[i].Name = "Subset_" + (i + 1).ToString(); // Start the task and pass the task information. threadTask[i].Start((object)ti); } ... // Wait for all threads to complete their task… WaitHandle.WaitAll(doneEvents); ... } /// <summary> /// Subset task method. /// </summary> /// <param name="state"></param> private void SubsetRasterBand(object state) { // The state object must be cast to the correct type, because the // signature of the WaitForTimerCallback delegate specifies type // Object. TaskInfo ti = (TaskInfo)state; //Deserialize the workspace connection properties. IXMLSerializer xmlSerializer = new XMLSerializerClass(); object obj = xmlSerializer.LoadFromString(ti.InputRasterWSConnectionProps, null, null); IPropertySet workspaceProperties = (IPropertySet)obj; ... }
线程同步
很多情况下,需要同步同时执行的多个进程。通常必须等待一个或多个线程完成任务,当特定事件发生,则可能会通知等待中的进程启动执行,或判断进程是否正在运行,或更改优先级,或其他指令。
.NET提供了管理运行中进程的多种方法,主要的类有:
- System.Threading.Thread类,用来创建、控制进程,更改优先级,获取进程状态。
- System.Threading.WaitHandle类,定义了信号机制用来控制对资源的独占,可以阻止访问代码块。
- System.Threading.Monitor类,类似于System.Threading.WaitHandle功能,用来同步进程。
- System.Threading.AutoResetEvent类和System.Threading.ManualResetEvent类,定义进程的相关事件。
Lock
采用Lock代码块,确保进程“写”安全:
[C#]
private DataTable m_locations = null; ... DataRow rec = m_locations.NewRow(); rec["ZIPCODE"] = zipCode; //ZIP Code. rec["CITYNAME"] = cityName; //City name. //Lock the table and add the new record. lock(m_locations) { m_locations.Rows.Add(rec); }
更新进程进度