在ArcObjects中使用多线程
Writing multithreaded ArcObjects code
Gisspace crazyspace@126.com
Gisspace crazyspace@126.com
Summary:Multithreading allows an application to do more than one thing at a time within a single process. This topic details what multithreading means in the context of the ArcObjects .NET SDK as well as the rules that must be followed to properly integrate threading into ArcObjects applications.
This topic does not attempt to give an introduction to multithreading or to teach multithreading concepts, but rather to give practical solutions for daily ArcObjects' programming problems that involve multithreading.
In this topic
1、介绍多线程
2、什么时候使用多线程
3、AO的线程模型
4、多线程情景假设
1)运行长时间操作以背景线程方式
2)使用线程池运行AO代码
3)同步执行一致的线程
4)通过多线程分享数据类型
5)通过背景线程更新用户界面
6)调用AO对象通过主线程外的其他线程
7) 通过多线程进行地理处理
Introduction to multithreading
多线程一般用来提高应用程序的响应速度。
Multithreading is generally used to improve the responsiveness of applications. This responsiveness can be either the result of real performance improvements or the perception of improved performance. By using multiple threads of execution in your code, you can separate out data processing and I/O operations from the management of your program's user interface (UI). This will prevent any long data processing operations from reducing the responsiveness of your UI.
The performance advantages of multithreading come at the cost of increased complexity in the design and maintenance of your code. Threads of an application share the same memory space, so you must make sure that accessing shared data structures is synchronized to prevent the application from entering into an invalid state or crashing. This synchronization is often called concurrency control.
(同步控制 )
Concurrency control can be achieved at two levels: the object level and the application level. It can be achieved at the object level when the shared object is thread safe,线程安全,对像保证所以的线程在访问他的时候都会等正在访问他的线程访问完毕。 meaning that the object forces all threads trying to access it to wait until the current thread accessing the object is finished modifying the object’s state. Concurrency control can be done at the application level by acquiring an exclusive lock on the shared object, allowing one thread at a time to modify the object’s state. You must be careful when using locks, as overuse of them will protect your data but can also lead to decreased performance. 过度的使用lock将降低性能Finding a balance between performance and protection requires careful consideration of your data structures and the intended usage pattern for your extra threads.
When to use multithreading
There are two items to consider when building multithreaded applications: thread safety 线程安全and scalability可测量性. It is important for all objects to be thread safe, but simply having thread-safe objects does not automatically mean that creating multithreaded applications is straightforward, or that the resulting application will provide improved performance.线程安全很重要 ,但是简单的线程安全不一定就会提高改良性能。
The .NET Framework allows you to easily generate threads in your application; however, writing multithreaded ArcObjects code should be done carefully. The underlying architecture of ArcObjects is Component Object Model (COM)Ao的底层框架是com,所以,必须明白。Net多线程和com多线程。. For that reason, writing multithreading ArcObjects applications requires an understanding of both .NET multithreading and the threading model for COM.
It is important to understand that multithreading will not always make your code run faster: in many cases it will add extra overhead and complexity that would eventually reduce the execution speed of your code. Multithreading should only be used when the added complexity is worth the cost. A general rule of thumb is that a task is suited to multithreading if it can be broken into different independent tasks.
多线程不一定提高性能,一个一般的原则是:如果他能够完成不同独立的任务那么他适合使用多线程。
The ArcObjects threading model
ArcObjects 组件是线程安全的,开发者能够在多线程环境中使用。
ArcObjects components are thread safe, and developers can use them in multithreaded environments. For ArcObjects applications to run efficiently in a multithreaded environment, the apartment threading model used by ArcObjects, 'Threads in Isolation', 去除跨线程通信must be considered. This model works by eliminating cross-thread communication. All ArcObjects references within a thread should only communicate with objects in the same thread.所以Ao组件都不是跨线程通信。
For this model to work, the singleton objects at ArcGIS 9.x were designed to be singletons per thread and not singletons per process. (单键型针对线程不是过程)The resource overhead of hosting multiple singletons in a process is outweighed by the performance gain of stopping the cross-thread communication that would occur if the singleton was created in one thread then accessed from the other threads. 数据在一个过程中可以有一个线程创建,另外一个线程访问。
As a developer of the extensible ArcGIS system, all objects, including those you write, must adhere to this rule for the Threads in Isolation model to work. If you are creating singleton objects as part of your development, you must ensure that these objects are singletons per thread, not per process.自己开发的ArcGIS产品同样也要准手这个原则。单键型针对线程不是过程
To be successful using ArcObjects in a multithreaded environment, programmers must follow the Threads in Isolation model while also writing their multithreaded code in such a way to avoid application failures such as deadlock situations, negative performance due to marshalling, and other unexpected behavior.
Multithreading scenarios
Although there are a number of ways to implement multithreading applications, below are a few of the more common scenarios that developers come across.例子
The ArcObjects .NET SDK includes several threading samples, referenced below, that cover the scenarios described in this topic. The samples demonstrate solutions for real-life problems while showing the best programming practices. While these samples use multithreading as part of a solution for a given problem, in some you will find that the multithreading is just an architectural aspect of a broader picture.
Running lengthy operations on a background thread
运行长时间操作以背景线程方式
When you are required to run a lengthy operation, it is convenient to allow the operation to run on a background thread while leaving the application free to handle other tasks and keep the UI responsive. Some examples of such operations include iterating through a FeatureCursor to load information into a DataTable and performing a complex topological operation while writing the result into a new FeatureClass.
当我们在把结果写入新的表格类中时,同时可以从数据表格中不断的加载数据到featurecursor和执行复杂的拓扑操作。
To accomplish this task there are a few points to keep in mind:(务必记住)
According to the Thread in Isolation model, you cannot share ArcObjects components between threads. Instead you will need to take advantage of the fact that singleton objects are ‘per thread’ and in the background thread, instantiate all factories required to open FeatureClasses, create new FeatureClasses, set spatial references, and so on.
依照线程独立模型,你不可能分享ArcObjects组件在线程之间。你必须利用的单键型对象的特点:单键型对象是单线程的。在背景线程中,实例化所有工厂需要打开FeatureClasses,产生FeatureClasses,设置空间参考等等。
All information passed to the thread must be in the form of simple types or managed types.
所有的信息传递到线程必须以简单的或者托管的数据格式。因此,当你需要传递一个AO对从主线程到工作线程时,序列化对象成string,传递字符串到目标线程,然后,反序列化回来。
比如:你能使用XmlSerializerClass去序列化一个对象如 工作空间相关的属性IPropertySet)
成字符串,传递其到工作线程与连接属性一起。这样,连接属性对象将创建一个背景线程,同时可以避免跨平台调用。
In cases where you must pass an ArcObject from the main thread into a worker thread, serialize the object into a string, pass the string to the target thread, and deserialize the object back. For example, you can use XmlSerializerClass to serialize an object such as workspace connection properties (an IPropertySet) into a string, pass the string with the connection properties to the worker thread, and deserialize the connection properties on the worker thread using the XmlSerializerClass. This way, the connection properties object will get created on the background thread and cross-apartment calls (调用)will be avoided.
当执行背景线程时,你能反馈工作任务当前进展到用户界面对话框。
While running the background thread, you can report the task progress onto a UI dialog. This is covered in more detail in the Updating UI from a background thread section of this topic.
The following example, an excerpt from the RSS Weather Layer sample, demonstrates a background thread that is used to iterate through a FeatureCursor and populate a DataTable that will be used later in the application. This keeps the application free to run without waiting for the table to be populated.
[C#] //Generate the thread that populates the locations table.
Thread t = new Thread(new ThreadStart(PopulateLocationsTableProc));
//Start the thread.
t.Start();
/// <summary>
/// Load the information from the MajorCities feature class to the locations table.
/// </summary>
private void PopulateLocationsTableProc()
{
//Get the ArcGIS path from the registry.
RegistryKey key = Registry.LocalMachine.OpenSubKey(@"SOFTWARE/ESRI/ArcGIS");
string path = Convert.ToString(key.GetValue("InstallDir"));
//Open the feature class. The workspace factory must be instantiated since it is a singleton per-thread.
IWorkspaceFactory wf = new ShapefileWorkspaceFactoryClass() as IWorkspaceFactory;
IWorkspace ws = wf.OpenFromFile(System.IO.Path.Combine(path, @"Samples Net/Data/USZipCodeData"), 0);
IFeatureWorkspace fw = ws as IFeatureWorkspace;
IFeatureClass featureClass = fw.OpenFeatureClass(m_sShapefileName);
//Map the name and zip fields.
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 location 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++;
}
//Release the feature cursor.
Marshal.ReleaseComObject(fCursor);
}
catch(Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}
Using ThreadPool to run ArcObjects code
Thread pooling enables you to use threads more efficiently by providing your application with a pool of worker threads that are managed by the system. The advantage of using a thread pool over creating a new thread for each task is that thread creation and destruction overhead is negated, which may result in better performance and better system stability.使用线程池好处多
Using ThreadPool tasks is highly efficient for operations that repeat themselves: handling client requests, listening on variable COM ports, subsetting multiple raster bands, and so on.主要用来执行反复的操作问题。如客服端请求,监听,子波段等问题。
When using a ThreadPool, there are a few things to keep in mind:(切记):
ThreadPools have an added advantage over the thread class. Not only do they allow you to create a thread, but they also let you pass an object associated with the thread to the thread delegate. This means that any information or data required to perform the ThreadPool task should be passed into that ThreadPool task through a user-defined class or structure. You should use the Threads in Isolation model to make sure all data is in the form of simple or managed types. 确保所有的数据一简单或者托管的形式。
You can monitor ThreadPool tasks the same way as any other thread. This includes waiting for multiple threads to finish their tasks, setting a specific thread to sleep, and resuming a waiting task.
There is no way to cancel a work item after it has been queued. 排队后就不可以取消。
The following example, an except from the Multithreaded Raster Subset sample, demonstrates using ThreadPool tasks to subset a RasterDataset, using a different task to subset each raster band.
[C#] /// <summary>
/// Class used to pass the task information onto 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 use the ThreadPool task.
Thread t = new Thread(new ThreadStart(SubsetProc));
t.Start();
}
/// <summary>
/// Main subset method
/// </summary>
private void SubsetProc()
{
...
//Create a threadpool task for each band of the input raster.
//Each task will subset a different raster band.
//All information required for subsetting the raster band will be passed to the threadpool task by the user-defined class TaskInfo.
for (int i = 0; i < m_intBandCount; i++)
{
TaskInfo ti = new TaskInfo(i, doneEvents[i]);
...
//Queue a method for execution, and specify the object containing the data to be used by the method (TaskInfo).
//The method will execute when the threadpool thread becomes available.
ThreadPool.QueueUserWorkItem(new WaitCallback(SubsetRasterBand), (object)ti);
}
}
/// <summary>
/// ThreadPool 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;
...
}
Synchronizing execution of concurrent running threads
同步执行当前线程
In many cases, you will have to synchronize the execution of concurrently running threads (both regular threads and ThreadPool tasks). Normally you will have to wait for one or more threads to finish their tasks, signal a waiting thread to resume its task when a certain condition is met, test whether a given thread is alive and running, change a thread priority, or give some other indication.
In .NET, there are several ways to manage the execution of running threads. The main classes available to help thread management are as follows:
System.Threading.Thread¾Used to create and control threads, change priority, and get status.
System.Threading.WaitHandle¾Defines a signaling mechanism to indicate taking or releasing exclusive access to a shared resource, allowing you to restrict access to a block of code.
Calling the WaitHandle.WaitAll() method must be done from a multithreaded apartment (MTA) thread. To launch multiple synchronized tasks such as ThreadPool tasks, you first have to launch a worker thread that in turn will generate the ThreadPool tasks. This keeps the application's main thread a single-threaded apartment (STA) thread.
System.Threading.Monitor¾Similar to System.Threading.WaitHandle, this class provides a mechanism that synchronizes access to objects.
System.Threading.AutoResetEvent and System.Threading.ManualResetEvent¾Used to notify waiting threads that an event has occurred, allowing threads to communicate with each other by signaling.
The following example, also in the Multithreaded Raster Subset sample, extends what was covered in the previous section. It uses the ManualResetEvent and the WaitHandle classes to wait for multiple threads to finish their tasks. In addition, it demonstrates using the AutoResetEvent class to block running threads from accessing a block of code and to signal the next available thread when the current thread had completed its task.
[C#] /// <summary>
/// Class used to pass the task information onto the working thread.
/// </summary>
public class TaskInfo
{
//Signal the main thread that the thread has finished its task.
private ManualResetEvent m_doneEvent;
...
public TaskInfo(int BandID, ManualResetEvent doneEvent)
{
m_bandID = BandID;
m_doneEvent = doneEvent;
}
...
public ManualResetEvent DoneEvent
{
get { return m_doneEvent; }
set { m_doneEvent = value; }
}
}
//Block access to the shared resource (raster dataset).
private static AutoResetEvent m_autoEvent = new AutoResetEvent(false);
...
public override void OnMouseDown(int Button, int Shift, int X, int Y)
{
...
//Run the subset thread that will use the ThreadPool tasking.
Thread t = new Thread(new ThreadStart(SubsetProc));
t.Start();
}
/// <summary>
/// Main subset method.
/// </summary>
private void SubsetProc()
{
...
//Create ManualResetEvent to notify the main threads that
//all ThreadPool threads are done with their tasks.
ManualResetEvent[] doneEvents = new ManualResetEvent[m_intBandCount];
//Create a threadpool task for each band of the input raster.
//Each task will subset a different raster band.
//All information required for subsetting the raster band will be passed to the threadpool task by the user-defined class TaskInfo.
for (int i = 0; i < m_intBandCount; i++)
{
//Create the ManualResetEvent field for the task to
// signal the waiting thread that the task had been completed.
doneEvents[i] = new ManualResetEvent(false);
TaskInfo ti = new TaskInfo(i, doneEvents[i]);
...
//Queue a method for execution, and specify the object containing the data to be used by the method (TaskInfo).
//The method will execute when the thread pool thread becomes available.
ThreadPool.QueueUserWorkItem(new WaitCallback(SubsetRasterBand), (object)ti);
}
//Set the state of the event to signaled to allow one or more of the waiting threads to proceed.
m_autoEvent.Set();
}
/// <summary>
/// ThreadPool 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 WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo)state;
...
//Lock all other threads to get exclusive access.
m_autoEvent.WaitOne();
//Insert code containing your threading logic here.
//Signal the next available thread to get write access.
m_autoEvent.Set();
//Signal the main thread that the thread has finished its task.
ti.DoneEvent.Set();
}
Sharing a managed type across multiple threads
分享托管类型通过多线程
如一个全局的表格,可能会有很多的线程访问。这时需要lock来锁定。
Sometimes your .NET application’s underlying data structure will be a managed object such as a DataTable or HashTable. These .NET managed objects allow you to share them across multiple threads such as a data fetching thread and a main rendering thread. However, you should consult the Microsoft Developer Network (MSDN) to verify whether an item is thread safe. In many cases, an object is thread safe for reading but not for writing. Some collections implement a Synchronized method, which provides a synchronized wrapper around the underlying collection.
In cases where your object is being accessed from more than one thread, you should acquire an exclusive lock according to the Thread-Safety section in MSDN regarding this particular object. Acquiring such an exclusive lock can be done using one of the synchronization methods described in the previous section or by using a lockstatement, which marks a block as a critical section by obtaining a mutual-exclusive lock for a given object. It ensures that if another thread attempts to access the object, it will be blocked until the object is released (exits the lock).
The following example demonstrates sharing a DataTable by multiple threads.
First, check the DataTable Class on MSDN to verify if it is thread safe.
On that page, check the section on Thread Safety, where it says:
This type is safe for multithreaded read operations. You must synchronize any write operations.
This means that reading information out of the DataTable is not a problem, but you must prevent other thread access to the table when you are about to write to it. The code below shows how to block those other threads.
[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);
}
Updating UI from a background thread
通过背景程序更新用户界面
在大多数情况下,当你在使用背景线程执行长时间操作时,你必须向用户报告进展,状态,错误等等。这个可以通过更新用户界面的某个控件实现。但是,在窗体,界面,控件中,必然不间断的的使用主线程但是不是线程安全的。所以,你必须使用代理,然后配置,当使用线程任何对用户界面的调用时。这个代理完成调用通过使用control。Invoke方法,其执行代理控件下面的窗口句柄线程。去确认是否一个调用这必须使用invoke方法。你能使用控件的InvokeRequered
属性,你必须确保控件句柄在Invoke或invokeRequired调用前以及被创建。
In most cases where you are using a background thread to perform lengthy operations, you will want to report to the user the progress, status, errors, or other information related to the task being done by the thread. This can be done by updating a control on the application's UI. However, in Windows, forms controls are bound to a specific thread (generally the main thread) and are not thread safe. As a result, you must delegate, and thus marshal, any call onto a UI control to the thread to which the control belongs. The delegation is done through calling the Control.Invoke method, which executes the delegate on the thread that owns the control's underlying window handle. To verify whether a caller must call an invoke method, you can use the property Control.InvokeRequired. You must make sure that the control's handle was created before attempting to call Control.Invoke or Control.InvokeRequired.
The following example, an excerpt from the RSS Weather Layer sample, demonstrates reporting a background task's progress into a user form.
Step 1: In the user form, declare a delegate through which you will pass the information to the control.
[C#] public class WeatherItemSelectionDlg : System.Windows.Forms.Form
{
private delegate void AddListItmCallback(string item);
…
Step 2: In the user form, set the method to update the UI control. Notice the call to Invoke. The method must have the same signature as the previously declared delegate:
[C#] //Make thread-safe calls to Windows Forms Controls.
private void AddListItemString(string item)
{
// InvokeRequired required compares the thread ID of the
// calling thread to the thread ID of the creating thread.
// If these threads are different, it returns true.
if (this.lstWeatherItemNames.InvokeRequired)
{
//Call itself on the main thread.
AddListItmCallback d = new AddListItmCallback(AddListItemString);
this.Invoke(d, new object[] { item });
}
else
{
//Guaranteed to run on the main UI thread.
this.lstWeatherItemNames.Items.Add(item);
}
}
Step 3: On the background thread, implement the method that will use the delegate and pass over the message to be displayed on the user form.
[C#] private void PopulateSubListProc()
{
//Insert code containing your threading logic here.
//Add the item to the list box.
frm.AddListItemString(data needed to update the UI control, string in this case);
}
Step 4: Write the call that launches the background thread itself, passing in the method written in step 3.
[C#] //Generate a thread to populate the list with items that match the selection criteria.
Thread t = new Thread(new ThreadStart(PopulateSubListProc));
t.Start();
Calling ArcObjects from a thread other than the main thread
从主线程外调用AO对象
In many multithreading applications, you will need to make calls to ArcObjects from different running threads. For example, you might have a background thread that gets a response from a Web service, which in turn should add a new item to the map display, change the map extent, or run a geoprocessing (GP) tool to perform some type of analysis.
在很多多线程程序中,你可能需要调用AO从不同的工作线程中,比如:你可能有一个背景线程是从网络服务中得到响应,然后需要把其在地图界面中展示,改变地图范围。或者执行某些分析操作。
一个更加常见的情形是:调用AO从timer控件的Elapse事件、而这个事件是线程池中的一个非主线程。但是他需要调用AO,看上去像是跨平台操作。然而,这种情况可以避免通过处理AO组件如:使用用户界面的控件,然后使用其Invoke代理去调用创建AO组件的主线程,这样就避免了跨平台。
A very common case is calling ArcObjects from a timer event handler method. Timer's Elapsed event is raised on a ThreadPool task, a thread that is not the main thread. Yet it needs to use ArcObjects, which looks like it would require cross-apartment calls. However, this can be avoided by treating the ArcObjects component as if it was a UI control and using Invoke to delegate the call to the main thread where the ArcObject was created. Thus, no cross-apartment calls are made.
The ISynchronizeInvoke interface includes methods Invoke, BeginInvoke, and EndInvoke. Implementing these methods yourself can be a daunting task. Instead, you should either have your class directly inherit from System.Windows.Forms.Control or you should have a helper class that inherits Control. Either option will provide a simple and efficient solution for invoking methods.
The following example uses a user-defined Invoke-Helper class to invoke a timer’s elapsed event handler to recenter the map’s visible bounds and set the map’s rotation. Note that some of the application logic must be done on the InvokeHelper class in addition to the user-defined structure that is being passed by the method delegate.
[C#] /// <summary>
/// A helper method used to delegate calls to the main thread.
/// </summary>
public sealed class InvokeHelper : Control
{
//Delegate used to pass the invoked method to the main thread.
public delegate void MessageHandler(NavigationData navigationData);
//Class members
private IActiveView m_activeView;
private IPoint m_point = null;
/// <summary>
/// Class constructor
/// </summary>
/// <param name="activeView"></param>
public InvokeHelper(IActiveView activeView)
{
//Make sure that the control was created and that it has a valid handle.
this.CreateHandle();
this.CreateControl();
//Get the active view.
m_activeView = activeView;
}
/// <summary>
/// Delegate the required method onto the main thread.
/// </summary>
/// <param name="navigationData"></param>
public void InvokeMethod(NavigationData navigationData)
{
// Invoke HandleMessage through its delegate.
if(!this.IsDisposed && this.IsHandleCreated)
Invoke(new MessageHandler(CenterMap), new object[] { navigationData });
}
/// <summary>
/// The method that gets executed by the delegate.
/// </summary>
/// <param name="navigationData"></param>
public void CenterMap(NavigationData navigationData)
{
//Get the current map visible extent.
IEnvelope envelope = m_activeView.ScreenDisplay.DisplayTransformation.VisibleBounds;
if (null == m_point)
{
m_point = new PointClass();
}
//Set the new map center coordinate.
m_point.PutCoords(navigationData.X, navigationData.Y);
//Center the map around the new coordinate.
envelope.CenterAt(m_point);
m_activeView.ScreenDisplay.DisplayTransformation.VisibleBounds = envelope;
//Rotate the map to the new rotation angle.
m_activeView.ScreenDisplay.DisplayTransformation.Rotation = navigationData.Azimuth;
}
/// <summary>
/// Control's initialization
/// </summary>
private void InitializeComponent()
{
}
/// <summary>
/// A user defined data structure used to pass information to the invoke method.
/// </summary>
public struct NavigationData
{
public double X;
public double Y;
public double Azimuth;
/// <summary>
/// struct constructor
/// </summary>
/// <param name="x">map x coordinate</param>
/// <param name="y">map x coordinate</param>
/// <param name="azimuth">the new map azimuth</param>
public NavigationData(double x, double y, double azimuth)
{
X = x;
Y = y;
Azimuth = azimuth;
}
/// <summary>
/// This command triggers the tracking functionality.
/// </summary>
public sealed class TrackObject : BaseCommand
{
//class members
private IHookHelper m_hookHelper = null;
…
private InvokeHelper m_invokeHelper = null;
private System.Timers.Timer m_timer = null;
…
/// <summary>
/// Occurs when this command is created.
/// </summary>
/// <param name="hook">Instance of the application</param>
public override void OnCreate(object hook)
{
…
//Instantiate the timer.
m_timer = new System.Timers.Timer(60);
m_timer.Enabled = false;
//Set the timer's elapsed event handler.
m_timer.Elapsed += new ElapsedEventHandler(OnTimerElapsed);
}
/// <summary>
/// Occurs when this command is clicked.
/// </summary>
public override void OnClick()
{
//Create the Invoke helper class.
if (null == m_invokeHelper)
m_invokeHelper = new InvokeHelper(m_hookHelper.ActiveView);
...
//Start the timer.
if(!m_bIsRunning)
m_timer.Enabled = true;
else
m_timer.Enabled = false;
...
}
/// <summary>
/// Timer elapsed event handler.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
private void OnTimerElapsed(object sender, ElapsedEventArgs e)
{
...
//Create the navigation data structure.
NavigationData navigationData = new NavigationData(currentPoint.X, currentPoint.Y, azimuth);
//Update the map extent and rotation.
m_invokeHelper.InvokeMethod(navigationData);
...
}
Multithreading with geoprocessing
To use GP in an asynchronous/multithreaded application, use the asynchronous execution pattern exposed in ArcGIS Server 9.2 using GP Services. This pattern enables the desktop working with GP in an asynchronous execution mode.
See Also:
Sample: Multithreaded raster subset
Sample: RSS Weather Layer
Sample: RSS Weather Layer 3D