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);
}

更新进程进度

posted @ 2011-09-26 18:38  xmwang  阅读(1533)  评论(1编辑  收藏  举报