ArcGIS Pro与旧的ArcGIS桌面应用程序的显著不同之处在于,它采用多线程架构,可以有效的发挥多核CPU的优势。这使得二次开发工具的性能变得更好,但也对开发工作带来了更多的难点和挑战。
当然,我们在ArcGIS Pro二次开发中也不用那么深入学习。因为ArcGIS Pro SDK的基础架构已经降低了代码的复杂性。
在大多数情况下,我们只需处理两个线程:【用户界面线程】和应用程序提供的单个【专用工作线程】。在内部,ArcGIS Pro使用大量线程来实现栅格化、图形渲染、数据加载以及利用并行性来加速计算的选择地理处理算法。但是这些线程完全是内部线程,和我们外部的开发没有关系。
当我们调用公共API中的方法时,可能会拆分为多个内部方法去实现,并将片段委托给一个或多个的专用内部线程。
ArcGIS Pro SDK 中的方法分为三类:
1、可在任何线程上调用的异步方法。这种类型的方法会加1个【async】修饰词,通常是返回任务。但是如果返回的是变量,则需要异步调用。
2、仅在工作线程上调用的同步方法。
3、仅在GUI线程上调用的同步方法。这些类型的方法通常与WPF相关联。
.NET引入了最重要的【async】和【await】语言功能。
【async】修饰符用于标记该方法,以便编译器知道该方法是异步的。
【await】则用于异步调用方法,然后强制调用线程自动返回到下一行并在异步操作完成后继续执行。
private async void Button_Click(object sender, RoutedEventArgs e)
{
double computedValue = await Task.Run(()=>
{
double result = 42.0;
return result;
});
// 在上面await后的内容执行完毕后,才会执行下面的MessageBox任务
MessageBox.Show(String.Format("Result was {0}", computedValue.ToString()));
}
以上面的代码为例,await后的异步方法执行完后,才会继续MessageBox.Show()方法。
输出结果为【Result was 42】。
虽然Task可以在任意的Add-in代码中运行,但ArcGIS Pro SDK框架提供了一个更优秀的QueuedTask,实际上在开发中,我们基本就只使用到这个QueuedTask。
这是一个自定义的任务调度程序,通过使用【QueuedTask.Run】用来调度ArcGIS Pro SDK中的同步方法的任务。这种调度本质是一个排队方法,可以让多个任务有序执行。
示例代码如下:
Task t = QueuedTask.Run(()=>
{
// 在此处调用同步SDK方法
});
相较于Task类,在ArcGIS Pro SDK中使用QueuedTask类有很多优势。
1、在任务主体中,开发人员可以访问ArcGIS Pro的各种对象,例如地图、图层、要素等。这些对象的访问是线程安全的,因为QueuedTask会自动管理与主线程之间的通信。
2、如果任务需要更新UI元素,例如修改地图视图或显示进度信息,开发人员可以使用QueuedTask的Dispatcher属性,将更新操作包装在Dispatcher.BeginInvoke方法中。这样可以确保更新操作在主线程上执行,避免线程冲突。
3、如果需要取消任务,开发人员可以调用QueuedTask.Cancel方法,该方法将触发任务的取消操作。被取消的任务会在取消后尽快退出,并且可以在任务主体中进行必要的清理工作。
4、如果任务执行过程中发生异常,QueuedTask会捕获异常并将其传递给开发人员,以便进行适当的处理。开发人员可以在任务主体中使用try-catch块来捕获并处理异常。
总之,ArcGIS Pro SDK中的QueuedTask提供了一种方便而强大的机制,用于在后台执行任务并安全地与ArcGIS Pro应用程序的UI线程进行交互。它的优点包括多线程支持、UI线程安全、可取消和异常处理等。通过合理地使用QueuedTask,开发人员可以实现高效、可靠的自定义功能和扩展。
QueuedTask任务的执行顺序
这里举2个例子来说明一下QueuedTask任务的执行顺序。
MessageBox.Show("1");
await QueuedTask.Run(() =>
{
MessageBox.Show("2");
MessageBox.Show("3");
});
MessageBox.Show("4");
以上面的代码为例,这是一个正常的、合理的异步执行。MessageBox的显示顺序为【1、2、3、4】。
但是如果将await的内容写成方法,并同步调用,情况则不同:
MessageBox.Show("1");
Spa();
MessageBox.Show("4");
public static async void Spa()
{
await QueuedTask.Run(() =>
{
MessageBox.Show("spa_1");
MessageBox.Show("spa_2");
});
}
执行上面的代码,MessageBox的显示顺序为【1、(spa_1、spa_2)、4】。变成了并行执行,很明显,这种执行方式顺序混乱,很容易造成错误。
不幸的是,至目前为止,我已经犯了很多这样的错。
因为个人习惯将一些通用的流程写成方法并调用,造成写了很多异步的方法,在调用的时候又直接同步调用。出了不少的BUG,调来调去才发现是异步并行的原因。
在上面代码的基础上进行修改,主代码中将自定义的方法改为异步执行,方法内部改为同步执行:
MessageBox.Show("1");
await QueuedTask.Run(() =>
{
Spa();
});
MessageBox.Show("4");
public static async void Spa()
{
MessageBox.Show("spa_1");
MessageBox.Show("spa_2");
}
运行结果,流程变回了串行执行,勉强解决了并行的问题。