首页 > 代码库 > [转载]ArcGIS Engine 中的多线程使用

[转载]ArcGIS Engine 中的多线程使用

 ArcGIS Engine 中的多线程使用

原文链接 http://anshien.blog.163.com/blog/static/169966308201082441114173/       

    一直都想写写AE中多线程的使用,但一直苦于没有时间,终于在中秋假期闲了下来。呵呵,闲话不说了,进入正题!

        大家都了解到ArcGIS中处理大数据量时速度是相当的慢,这时如果你的程序是单线程的,那可就让人着急坏了,不知道处理到什么地步,不能操作其他的功能,无奈~~如果在这时你能够想到用多线程技术,那就来试试该如何完成吧。

       首先,你得有点VS的多线程经验或学习经验,得知道什么多线程,代理(Delegate)是什么,同步与异步又是什么,等等。这些在VS的帮助文档中都有详细解释,在这里我就不越俎代庖了。我们其中精神去理解ArcGIS中多线程吧。

       在ArcgIS中,我们分几个部分阐述多线程。

       1、何时使用多线程

在创建多线程应用程序是应注意两点:线程的安全性和线程的伸缩性。线程安全对于所有的对象都是非常重要的,但是仅仅只有线程安全的对象并不意味着成功创建多线程应用程序,或者说线程安全能够提高应用程序的性能。

.NET框架允许你在应用程序中能够迅速的创建线程,但是,在编写ArcObjects代码的多线程必须要小心。ArcObjects最根本的结构是组件对象模型(COM)。从这一点来说,编写ArcObjects的多线程的代码需要既了解.NET多线程,又要了解COM多线程模型。

多线程并不总是使你的程序跑的很快,在许多情况下,它还会增加开支和复杂性,这些最终会减慢程序的执行速度。当增加的复杂性是值得的,那么多线程才能使用。一个基本的原则是,如果一个任务可以分解为不同的独立任务时,那这个任务是适合多线程的。

2、ArcObjects线程模型

所有的ArcObjects组件都被标记为单线程单元(STA参考VS帮助文档)。每个STA都限制在一个线程中,但是COM并不限制每个进程中STA的数目。当一个方法调用进入一个STA,它被转移到STA的唯一线程。因此,在STA中的一个对象将一次只接收和处理一个方法调用,它接收的每个方法调用会到达同一线程。

ArcObjects组件是线程安全的,开发者可把他们在多线程环境下使用。对于AO应用程序在多线程环境下有效运行,由AO所使用的线程单元模型,即独立线程,必须加以考虑。该模型的工作原理是消除跨线程通信。一个线程内所有ArcObjects对象的引用应当只与在同一个线程的对象进行通信。

对于此模型的运行,在ArcGIS 9.X中单个对象都被设计为线程唯一,而非进程唯一。在进程中管理多个对象的资源消耗超过由制止跨线程通信所获得的性能提升幅度。

对于扩展ArcGIS系统的开发者,所有对象甚至包括你创造的对象都必须遵循这一规则,孤立线程工作。如果你创建的对象做为开发的一部分,你必须确保它们是线程唯一,而不是进程唯一。线程唯一就是防止跨线程通信,这里ArcGIS Engine中多线程的首要规则。

3、多线程方案

尽管有很多实现多线程应用程序的方式,但以下几种方案是开发者经常使用的方式。

3.1、后台线程执行长事务

当要求需要长事务进行工作时,在后台执行长事务是可取的,并且同时让应用程序灵活的操作其他任务,并让界面处于响应状态。这一操作的例子很多,如:使用FeatureCursor来重复向DataTable装载数据,进行复杂的拓扑计算并写入新的FeatureClass。为了完成这类任务,请记住以下几点:

a. 根据在孤立模型中的线程,你不能在线程之间共享ArcObjects的组件。相反,你需要考虑的是,单个对象都在各自线程中,并在后台线程中,例如所有工厂需要打开FeatureClass,创造新的FeatureClass,设置空间参考等等。

b.传递给线程的所有信息必须是简单类型或托管类型的形式。

c.万一在某种情况下,你要从主线程向工作线程传递ArcObjects组件,可以将对象序列化成字符串,再将字符串传递给目标线程,然后再反序列化还原到对象。例如,你可以使用XmlSerializerClass序列化对象成为字符串,如工作区间(Workspace)连接属性(IPropertySet),再将这一字符串传递给目标线程,然后在工作线程中使用XmlSerializerClass反序列化连接属性。这样,就将连接属性对象在后台再次创造出来,从而避免了跨线程访问。

当运行后台线程,你能够在用户界面了解任务的进度。

3.2、实施单机ArcObjects的应用程序

正如微软开发人员网络(MSDN)网站上所说,“在.NET Framework版本2.0中,如果线程的单元状态在启动前尚未确定,新的线程就初始化为ApartmentState.MTA。主应用程序线程默认初始化为ApartmentState.MTA。您不能通过设置代码的第一行Thread.ApartmentState属性再设置主应用程序线程到ApartmentState.STA。而应使用STAThreadAttribute代替。”

作为ArcObjects的开发人员,这意味着,如果您的应用程序不被视为一个单一线程应用程序初始化的,.NET框架将为所有的ArcObjects创建一个特殊的单线程单元(STA)线程,因为他们被标记STA。这将导致对每一个从应用程序调用ArcObjects的线程切换到这个特定的线程上来。反过来,这迫使ArcObjects组件合在一起调用,并最终以COM组件调用可能慢了约50倍。幸运的是,这可避免通过简单地标记主要功能为[STAThread]。

3.3、使用托管线程池和BackgroundWorker的线程

线程池线程都是后台线程。线程池通过提供一个由系统管理的应用程序线程池使你使用线程更有效率。利用为每个任务创建一个新线程的线程池的优点是线程创建和销毁的开销是可忽略的,它可以带来更好的性能和更好的系统稳定性。

然而,设计的所有ThreadPool线程是在多线程单元(MTA),因此不应该被用来运行ArcObjects,它们是单线程单元。若要解决此问题,您有几种选择。一个是实现一个专用ArcObjects的线程,它被标记为STAThread并委派每个从MTA线程调用这个专用ArcObjects线程。另一种解决方案是使用自定义的STA线程池的实现,如标记为STA线程的线程数组来运行 ArcObjects。

3.4、同步运行线程的并发执行

在许多情况下,您必须同步执行的并发运行的线程。通常,你要等待一个或多个线程完成他们的任务,当一定条件下得到满足,一个等待线程的信号恢复其任务,条件如:测试是给定线否程激活和运行,改变线程优先级,或给予其他一些条件。

在.NET中有几种方法来管理运行线程的执行。可用来帮助线程管理的主要几类如下:

System.Threading.Thread;

System.Threading.WaitHandle;

System.Threading.Monitor;

System.Threading.AutoResetEvent and System.Threading.ManualResetEvent。

3.5、在多个线程共享一个托管类型

有时候你的.NET应用程序的底层数据结构将是一个如DataTable或哈希表管理的对象。这些.NET托管对象允许你在多个线程共享数据获取,如线程和主线程渲染他们。但是,您应该咨询MSDN Web站点以验证这一点是否是线程安全的。在许多情况下,一个对象是线程读安全,而写并不安全。有些集合实施同步方法,它提供了一个底层集合的同步包装。

在你的对象被多个线程访问的情况下,根据MSDN关于这种情况的对象线程安全规则,你应该获得一个独占锁。取得这样的独占锁能够完成上面所描述的同步方法,或使用lock语句,它通过获取给定对象的相互排他锁标签一个关键块。它可以确保,如果另一个线程试图访问对象时,它会被阻塞,直到该对象被释放(退出锁)。

3.6、从后台线程更新用户界面

在大多数情况下,您正在使用一个后台线程来执行长时间的操作,你想向用户报告进度,状态,错误或其他与该线程执行的任务相关的信息。这可以通过更新一个应用程序的用户界面控件来实现。但是,在Windows中,窗体控件绑定到一个特定的线程(通常是主线程),并且不是线程安全的。因此,你必须委派,从而结合,任何调用UI控件的线程来控制它的所属。该委托是通过调用Control.Invoke方法,该方法在线程上执行委托,该委托拥有控件的基础窗口句柄。要验证调用者是否必须调用Invoke方法,你可以使用属性Control.InvokeRequired。您必须确保该控件的句柄再尝试调用Control.Invoke或Control.InvokeRequired之前已经创建。

3.7、从一个线程调用ArcObjects而不是主线程

在许多多线程应用程序中,你将需要从不同线程调用AO组件。例如,你可能有一个后台线程来获取Web服务,这反过来,应该增加新的项目到地图显示,响应更改地图,或运行的geoprocessing(gp)的工具来执行某些类型分析。

一个非常常见的情况是从一个计时器事件处理方法调用ArcObjects。计时器的Elapsed事件是在一个线程池的任务提出,这不是一个主线程。然而,它需要使用ArcObjects,这好像是需要跨单元调用。然而,这可以避免处理ArcObjects的组件,就好像AO组件是一个用户界面控件和使用Invoke来调用委派到创建ArcObjects组件的主线程中。因此,没有跨单元调用。

ISynchronizeInvoke接口包括的方法有Invoke,BeginInvoke,和EndInvoke。自己实现这些方法可能是一个艰巨的任务。相反,你应该有你直接从System.Windows.Forms.Control继承的类或者你应该有一个助手类,它继承自控件。要么选择将提供一个简单而有效的对于调用方法的解决方案。

 

delegate SomethingClassType SomeDelegate(IArray array);

Func()

{

………………

            SomeDelegate del = new SomeDelegate(AnotherFunc);//AnotherFunc与SomeDelegate同样的形式

            IAsyncResult ireslt = del.BeginInvoke(array, null, null);//异步操作

 

            ProgressbarForm form = new ProgressbarForm();//异步操作中的进度条窗体

            form.setProgressBar("提示", "正在处理数据...", 10, 0, 100);

 

            form.Show();

            System.Windows.Forms.Application.DoEvents();

            while (!ireslt.IsCompleted)

            {

                System.Windows.Forms.Application.DoEvents();

            }

            SomethingClassType something= del.EndInvoke(ireslt);

            form.Close();

………

}

以上是理论方面的阐述及一个本人开发过程中的一个代码片段,希望这些能够帮助你完成你的多线程程序。参考的资料如下:Windows MSDN,ESRI 的开发者网站。