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);
}
更新进程进度