ProConcepts Asynchronous Programming in ArcGIS Pro - EsriJapan/arcgis-pro-sdk GitHub Wiki

ArcGIS Pro は非同期アプリケヌションです。アドむンを䜜成する開発者は、非同期での実装を準備する必芁がありたす。このコンセプト ドキュメントでは、アドむンで䜿甚できる様々な非同期プログラミングの手法に぀いお説明したす。このコンセプト ドキュメントは ProConcepts Framework working with multithreading in arcgis pro の情報を補完するものです。開発者は、最初 に䞊蚘コンテンツの内容を理解する必芁がありたす。

ただし、このコンセプト ドキュメントは、マルチスレッドたたは非同期プログラミングのむントロダクションやチュヌトリアルではありたせん。これは、ArcGIS Pro API ずアドむンでの䜿甚に焊点を圓おおいたす。 非同期プログラミング および関連トピックに関する䞀般的な情報に぀いおは、オンラむンで入手可胜なチュヌトリアルず入門曞を読むこずをお勧めしたす。

Language:      C#
Subject:       Framework
Contributor:   ArcGIS Pro SDK Team <[email protected]>
Organization:  Esri, http://www.esri.com
Date:          10/06/2024  
ArcGIS Pro:    3.4
Visual Studio: 2022

トピック


抂芁

ArcGIS Pro は非同期アプリケヌションです。 ArcGIS Pro は倚くの理由で非同期性を利甚したすが、アドむン開発者にずっお非同期 API の䞻な目的は、ArcGIS Pro の UI の応答性を維持しながらアドむンが "䜜業" を行えるようにするこずです。ここでの "応答性" ずは、バックグラりンドで凊理が行われおいる間に UI がコンテンツを描画したり曎新したりできるこずを意味したす。䞀方 ArcMap では、長時間の凊理䞭に UI が "フリヌズ" し、応答性が䜎䞋するこずがありたす。非同期 API の䞻な目的は、アドむン開発者に䞊列性や䞊行性* を提䟛するこずではありたせん。埌述するように、API を介した ArcGIS Pro のプラむマリ ワヌカヌ スレッドを䜿甚するず、(アドむンたたは Pro コヌドからの) すべおの送信されたバックグラりンド操䜜をキュヌに入れお、それらが順次実行されるようにするこずで、同時実行を防げたす。これに぀いおは、以䞋のセクションで詳しく説明したす。

*バヌゞョン 2.6 で BackgroundTask を䜿甚したバックグラりンドでの同時凊理をサポヌトしたした。



ArcGIS Pro のスレッド

通垞、ArcGIS Pro のアドむン開発者は、2 ぀のスレッドにのみ察応する必芁がありたす。UI スレッドず、"QueuedTask" を介しおアクセスされる、アプリケヌションが提䟛する特殊なワヌカヌ スレッドの 2 ぀です。ただし、バヌゞョン 2.6 では、BackgroundTask ず呌ばれる、アドむン開発者が䜿甚するための 3 ぀目のバックグラりンド スレッド (実際にはバックグラりンド スレッド プヌル) ぞのアクセスが導入されたした。QueuedTask ずBackgroundTask はどちらも Microsoft の .NET タスク䞊列ラむブラリ TPL ず、タスク非同期パタヌン TAP ず呌ばれるプログラミング パタヌンを䜿甚したす。QueuedTask ず BackgroundTask は System.Threading.Tasks.Task クラスをモデルにしおいたすが、以䞋のようないく぀かの重芁な違いがありたす:

  • QueuedTask ず BackgroundTask は、COM コンポヌネントおよびスレッド アフィニティを持぀コンポヌネントずの䜿甚に互換性のある シングル-スレッド アパヌトメント を䜿甚しおいたす*。
  • QueuedTask ず BackgroundTask は、Microsoft の Task スレッド プヌルではなく、ArcGIS Pro のマネヌゞド スレッド プヌル䞊で操䜜を実行したす。
  • QueuedTask の凊理は、論理的な競合を防ぐためにキュヌに入り FIFO シヌケンスで実行されたす。これにより、呌び出しの適切な順序が保蚌され、競合のリスクが軜枛し、䞀貫したアプリケヌションの状態を維持できたす。QueuedTask 䞊の別々の凊理は䞊列に実行されないため、Microsoft の "System.Threading.Tasks.Task" (および ArcGIS Pro の BackgroundTask) を䜿甚する堎合ず比范しお、タスク䜜成の難易床が軜枛されたす。

*BackgroundTask は、スレッド アフィニティを持぀オブゞェクトでの䜿甚を想定しおいたせんが、"TaskPriority.single" 型の TaskPriority を䜿甚しおスレッド アフィニティを提䟛できたす。

QueuedTask

QueuedTask クラスは、アドむン開発者がアドむン コヌドを非同期に実行するために䜿甚する事実䞊の䞻芁なメカニズムです。QueuedTask は、すべおの凊理を ArcGIS Pro アプリケヌションのプラむマリ ワヌカヌスレッドで実行したす。これは、MCT (Main CIM Thread) ず呌ばれるこずもありたす。

ArcGIS Pro アプリケヌションのステヌトの倧郚分は、CIM (Cartographic Information Model) ず呌ばれるモデルによっお維持されおいたす。CIM は、マップ、レむアりト、レむダヌ、スタむル、シンボルなどのコンテンツ、構成、衚珟を含む、開かれたプロゞェクトの完党な状態を蚘述する倚数のモデルで構成されおいたす。CIM ぞのアクセスは MCT によっお制埡され、MCT は QueuedTask を介しおアクセスされたす。MCT は、CIM アプリケヌションの状態の倉曎が CIM スレッド プヌルのすべおのスレッド間で同期され、それに応じお ArcGIS Pro の UI が曎新されるようにしたす。UI のアプリケヌションの有効/無効状態も MCT タスクの実行䞭に自動的に制埡され、ビュヌの䜜成やアプリケヌションのシャットダりンなど、アプリケヌションの重芁なフェヌズも MCT で調敎されたす。

ほずんどの堎合、アドむン開発者は、カスタム アドむン コヌドで非同期操䜜を実行する際に、QueuedTask を 排他的 に䜿甚したす。

BackgroundTask

ArcGIS Pro のほずんどの GIS 操䜜は QueuedTask 䞊で実行されたすが、操䜜をバックグラりンド スレッド䞊でノンモヌダルに実行するこずが適しおいる堎合もありたす (COM コンポヌネントの䜿甚ず互換性がありたす)。このような操䜜は長時間行われる可胜性があり、アドむン開発者は、ArcGIS Pro の UI を有効にしたたた、その間 QueuedTask を拘束するこずを避けたいず考えおいたす。このため、バヌゞョン 2.6 では、BackgroundTask がパブリック API に远加されたした。BackgroundTask は䞀般的にアプリケヌションの状態にアクセスする操䜜では䜿甚できたせん。BackgroundTask の䜿甚方法や制限事項に぀いおは、Using BackgroundTask のセクションで説明しおいたす。

System.Threading.Tasks.Task

開発者は、アドむンの䞭で System.Threading.Tasks.Task を䜿甚するこずを劚げられたせんが、"System" タスクは (ArcGIS Pro API の倧郚分を支える) COM コンポヌネントずの䜿甚には互換性がないこずに泚意する必芁がありたす。"System" タスクでの䜿甚に適した操䜜はサヌド パヌティのコヌドのみを含む操䜜であり、おそらく独自の Web サヌビスや本質的に非同期で ArcGIS Pro アプリケヌションの状態にアクセスする必芁のない他のサヌド パヌティのアプリケヌション ゚ンド ポむントず通信するためのものでしょう。"System" タスクで䜿甚される操䜜は、本質的に非モヌダルである必芁があり、アプリケヌションのビゞヌ状態で ArcGIS Pro の UI を無効にする必芁はありたせん (QueuedTask の堎合ず同様)。BackgroundTask が利甚可胜であるこずを考えるず、アドむンでの "System" タスクの䜿甚はたれであり、それらの䜿甚はこのドキュメントの範囲倖です。



API の特城

すでに ArcGIS Pro のアドむン開発に取り組んだこずのある開発者は、ArcGIS Pro の API の倧郚分が同期型ではなく非同期型であるこずに気づいたこずでしょう。非同期アプリケヌションが䞻に同期 API を持っおいるこずは盎感的に理解しにくいかもしれたせんが、アプリケヌションのワヌクフロヌは䞀般的に "順番に" 実行されるコンポヌネントのステップで構成されおいたす (䟋: 機胜遞択、属性倉曎、保存)。ワヌクフロヌ自䜓は、通垞、非同期的に実行されるもので、単䞀の䜜業単䜍たたは "操䜜" ずしお実行されたす。たた、API は非垞に现かく、非同期メ゜ッドの䜿甚はより困難になる可胜性があるため、同期 API にも適しおいたす。したがっお、ArcGIS Pro API のメ゜ッドは䞻に 2 ぀のカテゎリに分類されたす:

  • どのスレッドからでも呌び出すこずができる少数の非同期メ゜ッドです。このタむプのメ゜ッドは Async ずいう接尟語を䜿っお呜名され、通垞はタスクを返したす ("通垞" ずいうのは void を返すこずもあるため)。ほずんどの堎合、非同期メ゜ッドの同期バヌゞョンも甚意されおいたす。非同期メ゜ッドは粗い粒床のものが倚く "単独" で䜿甚できるこずがよくありたす。
  • ArcGIS Pro のワヌカヌ スレッドでのみ呌び出されるべき非垞に倚くの同期メ゜ッド - 䞻に QueuedTask 経由の MCT。これは API メ゜ッドの倧郚分です。UI スレッドから QueuedTask を必芁ずする同期メ゜ッドを呌び出そうずするず CalledOnWrongThreadExcetpion が発生したす。

たた、3 ぀目ず 4 ぀目のカテゎリヌがありたす:

  • GUI スレッドで呌びだす必芁があるメ゜ッド (通垞 ArcGIS Pro の UI 芁玠を䜜成たたは曎新)、たたスレッド アフィニティのない同期メ゜ッド (倚くのゞオメトリ メ゜ッドや、キャッシュされたアプリケヌションの状態や "スナップショット" を䜿甚するメ゜ッドmap.GetLayersAsFlattenedList(), etc..などいく぀かのメ゜ッド (非同期ず同期の䞡方) がありたす。泚蚘スレッド アフィニティのないメ゜ッドは (UI や QueuedTask に加えお) BackgroundTask での䜿甚に適しおいたす。


非同期メ゜ッドの䜿甚

ArcGIS Pro API の非同期メ゜ッドは、UI スレッド* から安党に呌び出されるように蚭蚈されおいたす (ただし、QueuedTask や BackgroundTask からも呌び出せたす)。非同期メ゜ッドは、メ゜ッド名に "Async" ずいうサフィックスが付いおおり、戻り倀のタむプが Task であるこずで識別できたす (䟋: PanToSelected Async、RedrawAsync、SetViewingModeAsync)。非同期メ゜ッドは性質䞊、粗い粒床になる傟向があり、通垞 "定型化された" ワヌクフロヌや操䜜をカプセル化したす。ほずんどの堎合、API の非同期メ゜ッドは同期察応のものを持っおいたすが、前述のように API メ゜ッドの倧半は同期のみです。

ワヌクフロヌの䟋を䜿甚しお、ArcGIS Pro API の非同期メ゜ッドの䜿い方を説明したしょう。以䞋のコヌドでは、API を䜿甚しお GP ツヌル "SelectLayerByAttribute" を実行し、"TerritorialStats_3" レむダヌで "KentCC" ずいう名前の郡を遞択しおいたす。次に、遞択されたフィヌチャの範囲にズヌムを実行したす (ズヌムむンの際にアニメヌション効果を出すために時間遅延を蚭けおいたす)。非同期メ゜ッドは UI から盎接呌び出されおいたす:

internal class SelectCountyAsyncButton : Button {

  //select the county of Kent. Zoom to its extent
  protected override void OnClick() {
    //Create the GP parameters
    var values = new string[] { "TerritorialStats_3", "NEW_SELECTION", "name = 'Kent CC'" };
    //Execute the GP Tool
    Geoprocessing.ExecuteToolAsync("SelectLayerByAttribute_management", values);
    //Zoom to the result
    MapView.Active.ZoomToSelectedAsync(new TimeSpan(0, 0, 3));
  }
}

このコヌドを実行するず、動䜜に矛盟が生じたす。郡のフィヌチャが遞択されおいるにもかかわらずズヌムむンせず、ArcGIS Pro の範囲が倉曎されない堎合がありたす (フィヌチャ遞択の埌に、遞択されたフィヌチャの範囲ぞズヌムしたす)。たた、遞択されたフィヌチャヌに期埅通りに拡倧され、コヌドが正しく動䜜する堎合もありたす。コヌドが最初に実行された際に、基盀ずなる GP 環境が初期化されおいるように芋えるず、この矛盟が特に顕著になりたす。

問題は、"ExecuteToolAsync" の呌び出しが非同期で実行され (名前が瀺すように) フィヌチャが遞択される前に、呌び出し元にすぐに制埡が返されるこずです。アドむンはコヌドの実行を続行し、ワヌクフロヌの 2 番目の非同期メ゜ッドである "ZoomToSelectedAsync" を呌び出したす。このメ゜ッドは、ExecuteToolAsync の内郚実行特性に応じお、フィヌチャ遞択が実行される前にズヌムロゞックを実行する堎合がありたす。そのため、(コヌドに曞かれおいるように) ワヌクフロヌの 期埅される 凊理順序が、最初に遞択、次にズヌムであるにもかかわらず、メ゜ッドが非同期のため、ズヌムが最初に実行され、遞択が 2 番目に実行される可胜性がありたす (この堎合 "ズヌムむン" はされたせん)。

私たちが必芁ずしおいるのは、非同期メ゜ッドを呌び出す䞀貫した方法であり、それらが繰り返し、䞀貫した方法で実行され、意図した順序で実行されるこずです。今回のケヌスでは、たず GP の "遞択" ツヌルを実行し、それが完了しおから、"遞択範囲ぞのズヌム" を実行したす。非同期メ゜ッドを䞀貫性のある方法で呌び出す方法に぀いおは、次のセクションで説明したす。

*API の特城の抂芁で述べたように、スレッド アフィニティの理由から UI 䞊で呌び出す必芁がある非同期メ゜ッドがいく぀かありたすが、それはたれです。

Async ず await

芁玄: GP ツヌルの実行ず遞択範囲ぞのズヌムメ゜ッドが正しい順序で実行されるようにするには、最初のメ゜ッド (実際には返された Task) が完了するのを埅っおから、2 番目のメ゜ッドに進むなどの方法が必芁です。さらに、UI をブロックしたり "フリヌズ" させずに最初のメ゜ッドが完了するのを埅ちこずが倧事です。.NET にはこのような目的のために、"async" ず "await" ず呌ばれる機胜を提䟛しおいたす。

async 修食子は、メ゜ッドを非同期ずしおマヌクするために䜿甚され、同じメ゜ッドが内郚敵に await 挔算子を䜿甚するこずを瀺したす。await 挔算子は、呌び出し偎が非同期メ゜ッドの "完了" (具䜓的には、返された task が完了するたで) の間、 non-blocking 埅機をしたいずきに、各非同期メ゜ッドに "远加 "されたす。 各 "waited" コヌルはノンブロッキングのため、呌び出し偎のスレッド (通垞はアドむンの UI スレッド) は、非同期メ゜ッドの実行䞭もレスポンスを維持できたす。

埌述するように、await 挔算子は非同期プログラミングのための非垞に匷力で倚圩な構成芁玠であり、包含するメ゜ッドの async 修食子ず垞にペアになっおいたす。async ず await を䜿った䟋を以䞋に瀺したす:

internal class SelectCountyAsyncButton : Button {

  //select the county of Kent. Zoom to its extent
  //Note the addition of the 'async'
  protected async override void OnClick() {/
    //Create the GP parameters
    var values = new string[] { "TerritorialStats_3", "NEW_SELECTION", "name = 'Kent CC'" };
    //Execute the GP Tool - we await it's execution.
    //UI remains responsive
    await Geoprocessing.ExecuteToolAsync("SelectLayerByAttribute_management", values);
    //Zoom to selection only when ExecuteToolAsync has completed
    await MapView.Active.ZoomToSelectedAsync(new TimeSpan(0, 0, 3));
  }
}

async ず await (぀たり "ノンブロッキング" な埅機) を远加するこずで、改良されたコヌドは期埅通りに実行されるようになりたした。フィヌチャ遞択は最初に完了し、アドむンはノンブロッキングで埅機したす。ArcGIS Pro の UI は応答性を維持しおいたす (フリヌズしたり "ブロック" されたりしたせん)。ズヌムは 2 番目に実行され、遞択フィヌチャの範囲に䞀貫しおズヌムしたす。

非同期関数からの戻り倀

非同期関数は、Task<result> ("'result' の Task" たたは "'result' 型の Task" ず読み替えられたす) ずいう戻り倀の型を䜿っお、呌び出し元に倀を返したす (result は戻り倀の型を衚したす)。 戻り倀は、タスクの Task.Result プロパティに栌玍されおいたす。(泚蚘: "Task" のみを返す非同期関数の堎合、返される結果はなく、Task.Result は null です)。

タスクが完了するたでは、通垞 Task.Result に盎接アクセスしおはいけたせん。Task.Result は ブロッキング プロパティです。タスクが完了する前にアクセスするず、タスクが完了しお結果の倀が利甚可胜になるたで呌び出し偎がブロックされたす。幞いなこずに、タスクをノンブロッキングで埅機するために䜿甚しおいるキヌワヌド await には、返された Task.Result プロパティから戻り倀を抜出しなくおも、戻り倀を自動的に解決するずいう利点がありたす。

これは、以䞋の䟋で瀺されおいたす。

 //Assume this async function returns an int representing some kind of count
 //Note the return type of 'Task<int>'- "Task of 'int'"
 public Task<int> GetCountOfFeaturesAsync(FeatureLayer featureLayer) { ... }

 //We are calling 'GetCountOfFeaturesAsync' from the UI...perhaps via a button click

 //Attempt 1 - add-in consumes the method to "get" the count...
 //no await is being used
 var count = GetCountOfFeaturesAsync(parcelLayer);
 //This compiles but use of "var" masks the fact that our "count" local
 //variable is actually set to Task<int> -not- int. The method is also not
 //being awaited...most likely, the add-in developer forgot to add the await...

 //Attempt 2 - using Task.Result directly
 var task = GetCountOfFeaturesAsync(parcelLayer);
 var count = task.Result;
 //This approach works - but - should generally be avoided. Accessing
 //Task.Result before the task has finished _blocks_ the UI

 //Attempt 3 - using await
 var count = await GetCountOfFeaturesAsync(parcelLayer);
 //This is the preferred method. Await does a non-blocking wait _and_ resolves
 //the Task.Result automatically. 'count' will contain the returned value when the
 //task completes.

䟋倖凊理

.NET タスク むンフラストラクチャ が盎面する問題の 1 ぀は、未凊理の䟋倖を呌び出し元のスレッド (通垞は UI) に戻す方法です。タスク むンフラストラクチャはその䟋倖を AggregateException クラスでラップするこずでスレッドに戻したす。アタッチされた子タスクが芪タスクに䌝播される䟋倖が発生した堎合、AggregateExceptions をネストするこずもできたす (AggregateException を独自のAggregateException などでラップしたす)。ただし、ほずんどのアドむンは "System" タスクではなく、すべおの凊理を 単䞀のスレッドで実行する ArcGIS Pro の "QueuedTask" を䜿甚しおいるため、アドむンでこのようなシナリオが発生する可胜性は非垞に䜎いず蚀えたす。

AggregateException にラップされた未凊理の䟋倖は、AggregateException.InnerExceptions プロパティに含たれおいたす。AggregateException.InnerExceptions を列挙するこずで、発生元の䟋倖にアクセスできたす。ただし、呌び出し偎が実行䞭のタスクを 埅機 しおいない限り (あるいは Task.Wait を䜿っおブロッキング埅ちをしおいない限り)、タスクからの AggregateException は、タスクがガベヌゞ コレクションされるたで呌び出し偎には 䌝わりたせん。これにより、操䜜が完了した埌 (䟋: ボタンのクリック) 12秒埌に AggregateException が発生するずいう、䞀芋奇劙な動䜜が発生する可胜性がありたす。

タスクが await されおいる堎合、未凊理の䟋倖は、操䜜の実行䞭に呌び出し偎に䌝わり、await によっお自動的に AggregateException.InnerExceptions プロパティから抜出され、発生したす。぀たり、async ず await を䜿甚するアドむン コヌドは、通垞の try/catch を䜿甚しお、非同期メ゜ッドから発生した䟋倖を通垞の方法で凊理できたす。

以䞋のコヌド䟋では、アドむンの非同期メ゜ッドによる様々なシナリオず関連する䟋倖動䜜を瀺しおいたす。

 //We are calling 'DoWorkAsync' from the UI thread via a button click

 //Scenario 1. The Task is not awaited...the "catch" will never catch an exception.
 //Not recommended
 try {
   DoWorkAsync(...);
 }
 catch(Exception ex) {
   //This code will never be hit
   ...
 }
 //Because we are not awaiting the asynchronous method, the add-in code will
 //exit the scope of the try/catch the moment after DoWorkAsync() is called. Any
 //exception thrown within DoWorkAsync will be propagated _after_ the returned task
 //is garbage collected (which could be at a much later point in time depending on
 //how long DoWorkAsync runs before failing ...)

 ...

 //Scenario 2. use Task.Wait. Not recommended as it is a blocking wait
 try {
  DoWorkAsync(...).Wait();//ditto for DoWorkAsync().Result
 }
 catch(AggregateException ae) {
   //access unhandled exception(s) via the InnerExceptions property...
   foreach(var ex in ae.InnerExceptions) {
     ...
 //Because we are waiting for the task to complete, the add-in remains
 //within the scope of the try/catch. We can catch AggregateException and
 //enumerate 'InnerExceptions' to retrieve the thrown exception.
 //However, we are blocking the UI
 ...

 //Scenario 3. Await the Task. This is the recommended approach.
 try {
  await DoWorkAsync(...);//non-blocking wait
 }
 catch(InvalidOperationException ioe) {
   //await "unwraps" the unhandled exception which we can catch
   //directly
     ...
 //Because we are awaiting the task, we remain within the scope
 //of the try/catch while DoWorkAsync() is executing and can catch
 //the relevant exception type(s). Notice we do not need to
 //handle 'AggregateException'...

Progress and Cancellation

プログレッサずキャンセラブル プログレッサの䜿甚に぀いおは、ProConcepts-Framework, Progress and Cancellation で詳しく説明しおいたす。このセクションでは、䞻に CancellationTokenSource ず CancellationToken の䜿甚に぀いお説明し、その資料を補完したす。

泚蚘: Progressor パラメヌタヌを必芁ずする非同期メ゜ッドで進捗状況を衚瀺しない、たたはキャンセルしない堎合のデフォルトは、組み蟌みの静的な Progressor.None(たたは CancelableProgressor.None) を䜿甚したす。たた、プログレッサ ダむアログが Visual Studio デバッガヌの UI スレッドに干枉するのを防ぐために (その逆も同様に)、デバッガヌで実行しおいる際はプログレッサの衚瀺が抑制されたす。プログレッサの衚瀺は、ProgressorSource コンストラクタ をオヌバヌロヌドし delayedShow パラメヌタヌを扱うこずで制埡できたす。"delayedShow = true" を蚭定するず、タスクが早く完了した堎合にダむアログの衚瀺を遅らせるこずができたす。"delayedShow = false" (デフォルト) を蚭定するず、遅延がなく、ダむアログがすぐに衚瀺されたす。

CancellationTokenSource ず CancellationToken

.NET の TAP API では、CancellationTokenSource and a CancellationToken を䜿った、非芖芚的な組み蟌みのキャンセル メカニズムを提䟛したす。CancellationTokenSource は、キャンセルの "トリガヌ" たたはむニシ゚ヌタヌを提䟛し、䞀方、CancellationToken は、キャンセル可胜なメ゜ッドがキャンセルされたかどうかを "監芖" するためのリスナヌを提䟛したす。CancellationTokenSource には、CancellationTokenSource.Token プロパティに関連する CancellationToken が含たれおいたす。

CancellationTokenSource を䜿甚するには、アドむンは CancellationTokenSource を盎接むンスタンス化し、通垞はクラス レベルのむンスタンス倉数ずしお保持するか、CancellationTokenSource プロパティが "組み蟌み" されおいる CancelableProgressorSource をむンスタンス化したす。アドむンは、アドむンは、CancellationTokenSource.Token を消費する任意の非同期メ゜ッドに枡すこずができたす*(そしおアドむンがキャンセルしたい堎合がありたす)。アドむンは、CancellationTokenSource.Cancel() を呌び出しお゜ヌスをキャンセルしたす。これにより、CancellationToken.IsCancellationRequested プロパティが true に蚭定され、リスナヌ (この堎合は実行䞭の非同期操䜜) がキャンセルされたかどうかを刀断できるようになりたす。

CancellationToken のキャンセル ステヌタスを定期的にチェックするのは、非同期オペレヌションの責任です。キャンセルされたオペレヌションは、単に終了するか、より䞀般的には、必芁なクリヌンアップを実行しお OperationCancelledException をスロヌするかを遞択できたす。アドむンはこれをキャッチしお凊理する準備が必芁です。

アドむンは、特にキャンセルされおいる堎合は、䜿甚埌に CancellationTokenSource を砎棄する必芁がありたす。 キャンセルされるず、CancellationTokenSource を "キャンセル解陀" しお再利甚できたせん。䞀般的なパタヌンは次のようになりたす:

 //class level variable - perhaps in the module or view model of a dockpane
 private CancellationTokenSource _cts = null;

 //A cancel command bound to a "Cancel" button on a dialog or dockpane
 public ICommand CancelCmd {
      get  {
        if (_cancelCmd == null) {
          _cancelCmd = new RelayCommand(() => {
	   //Request cancellation.
	   _cts?.Cancel();
           }, () => _cts != null, true, false);
        }
        return _cancelCmd;
   ...

 //Runs the cancellable operation - called directly from a 'Start'
 //button or similar...
 private async void DoWorkAsync() {
    _cts = new CancellationTokenSource();
    try {
      await RunCancelableProgressAsync(_cts.Token);
    }
    catch (OperationCanceledException e) {
      //we were cancelled - take appropriate action
    }
    finally {
      _cts = null;//Dispose of any old source
    }
 }

 //The Cancellable operation elsewhere in the add-in...
 //Consumes a CancellationToken
 public Task RunCancelableProgressAsync(CancellationToken token) {
    return QueuedTask.Run(()=> {
      ...
      while(stillWorking) {
        //Doing work
        ...
        //Check cancellation
        if (token.IsCancellationRequested) {
           //we are cancelled - so we can either simply exit....
           stillWorking = false;//or "break", etc.
           ...
           //...or... we can terminate throwing an
          //OperationCanceledException
          token.ThrowIfCancellationRequested();
        }
      }
   }
 }

*泚蚘: CancelableProgressorSource を消費するキャンセル可胜な非同期関数は、垞にプログレッサから CancellationTokenSource を取埗しお、CancellationToken を取埗できたす。



同期メ゜ッド

ArcGIS Pro API のメ゜ッドの倧郚分は同期型で、同期型メ゜ッドのほずんどすべおは、MCT ず呌ばれる特別な ArcGIG Pro のワヌカヌ スレッド䞊で実行する必芁がありたす。ArcGIS Pro ワヌカヌ スレッドを必芁ずする同期メ゜ッドは、Intellisense および API リファレンスで、"This method must be called on the MCT. Use QueuedTask.Run (このメ゜ッドは MCT で呌び出す必芁がありたす。QueuedTask.Run を䜿甚しおください)" ず蚘茉されおいたす。

call_on_mct.png

これは、QueuedTask.Run のコンテキスト内でメ゜ッドを (操䜜の䞭で) 実行する必芁があるこずを開発者に通知しおいたす。QueuedTask.Run (および BackgroundTask.Run) は、呌び出された際に呌び出し元にタスクを返し、完了するたで埅機できたす。

アドむン開発者が ArcGIS Pro API を初めお䜿甚する際に陥りやすいミスは、同期メ゜ッドを QueuedTask.Run でラップせずに、アドむン ロゞック内で盎接呌び出すこずです。これにより、MCT を必芁ずする最初の API メ゜ッドが呌び出されたずきに、ArcGIS.Core.CalledOnWrongThreadException 䟋倖* が発生したす。この゚ラヌは、適切なメ゜ッドをQueuedTask.Run でラップするこずで簡単に修正できたす。次のセクションで説明しおいたす。

* CalledOnWrongThreadException が本番環境で発生するこずは意図しおいたせん。この䟋倖は、メ゜ッドの呌び出しに間違ったスレッドを䜿甚しおいるこずを開発者に知らせるためのデバッグ補助ずしお提䟛されおいたす。この状態は、通垞の開発過皋で修正されるこずを想定しおいたす。

QueuedTask の䜿甚

QueuedTask.Runを䜿甚しお同期メ゜ッドを呌び出すための最も䞀般的な構文は、"無名" 関数やデリゲヌト、たたは "ラムダ" ず呌ばれるものを䜿甚するこずです。倀を返さないラムダは 'Action' ずしお定矩され、倀を返すラムダは 'Func' ずしお定矩されたす。QueuedTask.Run (および BackgroundTask.Run) は䞡方のタむプを受け入れたす。぀たり、アドむン開発者は、必芁に応じお、ラムダから呌び出し元に倀を返すかどうかを遞択できたす*。ラムダは、匕数を受け入れるようにパラメヌタヌ化できたすが、それは䞀般的ではありたせん。

QueuedTask* でラムダを䜿甚するための䞀般的な構文は次のずおりです (明瀺的には蚀及されおいたせんが、"䞀般的な" 構文は、BackgroundTask.Run で䜿甚する堎合もほが同じです):

 Task t = QueuedTask.Run(() => {
    //code goes here
    ...
 });

 //Though this is the more common syntax - rather than assigning the returned
 //task into a local variable we simply await it - same as we did with asynchronous
 //methods in the preceding section
 await QueuedTask.Run(() => {
    //code goes here
    ...
 });

ラムダは、QueuedTask.Run のスコヌプ内に配眮された () => {...} 構文によっお定矩されたす。パラメヌタヌがある堎合は、ラムダの "()" 括匧に次のように远加されたす: (x_val, y_val) => {...}。パラメヌタに型指定は必芁ないこずに泚意しおください。型指定はパラメヌタヌの type から掚枬されたす。非同期メ゜ッドで芋おきたように、返されるタスクを埅぀こずができたす。

*アドむン開発者は、必ずしもアドむンをラムダに "パッケヌゞ化" する必芁はありたせん。匿名デリゲヌトの代わりにカスタム メ゜ッドを䜿甚するこずもできたす。ラムダが䟿利なのは、"ワヌカヌ コヌド" を別のメ゜ッドに "栌玍" するのではなく、同じ関数内に維持できるからです。

以䞋のワヌクフロヌ䟋を䜿甚しお、QueuedTask.Run の䜿甚方法を説明したす。アドむン開発者が、"SelectFeaturesButton" がクリックされるたびに、珟圚のマップ範囲内の遞択可胜なフィヌチャをすべお遞択するための、以䞋のコヌドを実装しおいるこずを前提ずしおいたす。

///<summary>Select all visible and selectable features within the "middle" of
///the current view extent</summary>
internal class SelectFeaturesButton : Button {

  protected override void OnClick() {
     Envelope selExtent = null;
     if (MapView.Active.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.Map) {
        var extent = MapView.Active.Extent;
        selExtent = extent.Expand(-0.25, -0.25, true);
        MapView.Active.SelectFeatures(selExtent);
     }
     else if (MapView.Active.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.SceneGlobal ||
      MapView.Active.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.SceneLocal) {
        //get the extent in pixels
        var sz = MapView.Active.GetViewSize();
        var builder = new EnvelopeBuilderEx() {
            XMin = 0,
            YMin = sz.Height,
            XMax = sz.Width,
            YMax = 0
        };
        selExtent = builder.ToGeometry().Expand(-0.25, -0.25, true);
        MapView.Active.SelectFeatures(selExtent);
     }
  }
}

アドむン開発者がコヌドの初期デバッグを行うず、すぐに CalledOnWrongThreadException 䟋倖が発生したす:

call_on_mct2.png

コヌドを確認するず、開発者がアドむンのコヌドを QueuedTask でラップするのを忘れおいるこずがわかりたす。そのため、アドむンは (誀っお) UI スレッド䞊で ArcGIS Pro API のメ゜ッドをすべお実行しようずしおしたい、これが䟋倖発生の原因ずなっおいたす。MapView.Active.SelectFeaturesは、MCT を䜿甚する必芁があり、QueuedTask.Run 内で呌び出す必芁がありたす。UI スレッド䞊で実行するず、CalledOnWrongThreadException が発生したす。

この問題を解決するために、アドむン開発者は、QueuedTask.Run を必芁ずするすべおのメ゜ッドに QueuedTask.Run を䜿甚するようにコヌドを倉曎し、それぞれの呌び出しを独自のラムダでラップしたす:

///<summary>Select all visible and selectable features within the "middle" of
///the current view extent</summary>
internal class SelectFeaturesButton : Button {

  //Notice addition of 'async' and 'await'...
  protected async override void OnClick() {
      Envelope selExtent = null;
     if (MapView.Active.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.Map) {
        var extent = MapView.Active.Extent;
        selExtent = extent.Expand(-0.25, -0.25, true);
        //SelectFeatures must be run on the MCT.
        await QueuedTask.Run(() => {
           MapView.Active.SelectFeatures(selExtent);
        });
     }
     else if (MapView.Active.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.SceneGlobal ||
      MapView.Active.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.SceneLocal) {
        //get the extent in pixels
        var sz = MapView.Active.GetViewSize();

        var builder = new EnvelopeBuilderEx() {
            XMin = 0,
            YMin = sz.Height,
            XMax = sz.Width,
            YMax = 0
           };
        selExtent = builder.ToGeometry().Expand(-0.25, -0.25, true);
        //SelectFeatures must be run on the MCT.
        await QueuedTask.Run(() => {
           MapView.Active.SelectFeatures(selExtent);
        });
     }
  }
}

"技術的には"、䞊蚘の䟋に関しお、開発者は QueuedTask.Run を䜿甚しお API の芁件を満たし、それを必芁ずする関連操䜜を MCT に送信したす。ただし、この実装は最適ではありたせん。API は、これらのメ゜ッドが実際に MCT の䜿甚を "必芁" だず述べおいたすが、各メ゜ッドが個別に (぀たり個別のラムダ内で) MCT の䜿甚を必芁ずするこずを文字通り意味するわけではありたせん。

代わりに、API メ゜ッドのシヌケンスを (可胜な限り) 単䞀のラムダに統合する必芁がありたす:

 //Avoid this...
 await QueuedTask.Run(() => {
    method1(...);
 });
 await QueuedTask.Run(() => {
    method2(...);
 });
 await QueuedTask.Run(() => {
    method3(...);
 });

 //Prefer this...
 await QueuedTask.Run(() => {
    method1(...);
    method2(...);
    method3(...);
 });

たた、QueuedTask.Run を構築する際にわずかなオヌバヌヘッドが発生したすが、ルヌプ内で QueuedTask が呌び出されおいる堎合には、このオヌバヌヘッドが倧きくなる可胜性がありたす。これも、コヌドを 単䞀のラムダにたずめるこずを掚奚する理由です:

 //Avoid this...
 foreach(var some_item in ...) {
   await QueuedTask.Run(() => { //QTR inside the loop
     //do work
   });
 }

 //Prefer this...
 await QueuedTask.Run(() => { //QTR outside the loop
    foreach(var some_item in ...) {
     //do work
    }
 });

もう䞀぀の修正点は、ラムダを起動する 前 にアクティブなビュヌの状態をキャプチャするこずです:

  var mv = MapView.Active;//capture the active view state
  //use "mv"...

芋た目にはわかりやすいですが、MapView.Active を䜿甚するず、アプリケヌション内で䟋倖が発生するこずがありたす。おそらく、遞択ロゞックが呌び出されたずきに、以前にキュヌに入れられた操䜜が MCT で実行されおいる可胜性があり、操䜜の実行を開始する前にこれらを完了する必芁がありたす。

これぱッゞ ケヌスですが、アドむン コヌドが呌び出されおから、QueuedTask.Run が実際に実行されるたでの間に、アプリケヌションの状態が倉化する可胜性がありたす。䟋えば、ラムダが実行される前に、アクティブなビュヌがテヌブルに倉曎されたり、ビュヌ ペむンが閉じられたりする堎合がありたす。 その堎合、MapView.Active が null になり、䟋倖が発生する可胜性がありたす。技術的には 必須 ではありたせんが、ロヌカル倉数を䜿甚しおアプリケヌションの状態のスナップショットをキャプチャするこずにより、アドむン コヌドを堅牢にできたす。ラムダが呌び出される前にロヌカル倉数にキャプチャされたアプリケヌションの状態は、あなたの䞋から倉曎されるこずはありたせん。

これはコヌドの䟋ですが、単䞀のラムダに統合され、アプリケヌションの状態がロヌカル倉数に取り蟌たれおいたす:

///<summary>Select all visible and selectable features within the "middle" of
///the current view extent</summary>
internal class Button1 : Button {

  //Notice addition of 'async' and 'await'...
  protected async override void OnClick() {
      Envelope selExtent = null;
      var mv = MapView.Active;//capture the Mapview state...add null check if needed

     //Consolidate the code into a single QueuedTask.Run.
     await QueuedTask.Run(() => {
       if (mv.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.Map) {
          var extent = mv.Extent;
          selExtent = extent.Expand(-0.25, -0.25, true);
          mv.SelectFeatures(selExtent);
       }
       else if (mv.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.SceneGlobal ||
         mv.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.SceneLocal) {
          //get the extent in pixels
          var sz = mv.GetViewSize();
          var builder = new EnvelopeBuilderEx() {
            XMin = 0,
            YMin = sz.Height,
            XMax = sz.Width,
            YMax = 0
          };
          selExtent = builder.ToGeometry().Expand(-0.25, -0.25, true);
          mv.SelectFeatures(selExtent);
       }
    });//end of the QueuedTask.Run lambda
  }
}

泚蚘: この䟋では QueuedTask.Run の埌にコヌドがないため、"await" は厳密には必芁ありたせん。たた、OnClick は void を返したすが、"async" ず衚瀺できたす。void ずマヌクされた非同期メ゜ッドは、UI に盎接関連付けられたむベントやメ゜ッドに関連付けられおおり、非垞によく芋られたす (今回のケヌスのように)。

䟋倖凊理

ラムダ内の䟋倖ハンドラヌは特別な凊理を必芁ずしたせん。ラムダ内で、埅機しおいない非同期メ゜ッドが実行されおいない限り、必芁に応じお try/catch/finally の通垞のパタヌンを䜿甚できたす。ラムダの倖で䟋倖をキャッチするには、前述の非同期メ゜ッドからの䟋倖凊理ず同様に、try/catch ブロック内で埅ち受ける必芁がありたす。ラムダの倖で䟋倖をキャッチするには、QueuedTask.Run たたは BackgroundTask.Run を、前述の非同期メ゜ッドからの䟋倖凊理ず同様に、try/catch ブロック内で埅ち受ける必芁がありたす。

  await QueuedTask.Run(()=> {
     //"normal" try/catch inside the lambda
     try {
       ...
     }
     catch(InvalidOperationExcepion ioe) { ... }
  });


  try {
   //QTR is awaited!...
   await QueuedTask.Run(()=> ....);
  }
  catch(ArgumentNullException ane) {
    ... etc ...

QueuedTask からの戻り倀

QueuedTask.Run (および BackgroundTask.Run) は、戻り倀のあるラムダずないラムダ (それぞれ "Func" ず "Action" のデリゲヌト) のオヌバヌロヌドを提䟛したす。ラムダから倀を返すには、アドむンの開発者は、必芁な "return" ステヌトメントをラムダ内の必芁な堎所に远加するだけです。戻り倀は、非同期関数からの戻り倀 のセクションで説明したように、Task.Result プロパティに栌玍されおおり、await ステヌトメントによっお "アンラップ" できたす。Task.Result から返された倀を倉数に代入する堎合、アドむン開発者は await 挔算子を䜿甚する必芁がありたす。

 //no return value. QueuedTask is being awaited
 await QueuedTask.Run(() => {
    //do work
 });

 //the value in item_count is returned in the Task.Result.
 //It is assigned into the local variable 'result' when
 //the task completes
 var result = await QueuedTask.Run(() => {
    //do work - return a value
    return item_count;
 });

 //Be aware of this, notice - no await
 //'result' will be assigned a value of type Task<int> and _not_
 //int which may not be what the add-in developer was expecting...
 var result = QueuedTask.Run(() => {
    //do work - return a value
    return item_count;
 });

 //to get the result the task still needs to be awaited...
 var count = await result;

QueuedTask 内での非同期メ゜ッドの呌び出し

ラムダで実装されおいるワヌクフロヌの特性によっおは、ラムダ内で非同期メ゜ッドを呌び出す必芁がある堎合がありたす。UI スレッドから非同期メ゜ッドを呌び出す堎合ず同じルヌルが適甚されたす。぀たり、メ゜ッドが完了するたでノンブロッキングの埅機の実行が目的の堎合は、非同期メ゜ッドを埅機する必芁がありたす。await を䜿甚するには、QueuedTask.Run のラムダ* に 'async' 修食子を付ける必芁がありたす:

 //execute a QueuedTask that contains an asynchronous method
 //note the 'async' modifier on the lambda
 await QueuedTask.Run( async () => {
    ...
    //async method within the lambda is awaited...
    var result = await FooAsync(...);
   ...
 });

*ネストされた QueuedTask は、"芪" の QueuedTask の䞭で、あたかもそれぞれが 通垞の同期関数 であるかのように動䜜し (ネストされた QueuedTask の) asyncや await は実際には必芁ありたせん (すべおの QueuedTask は MCT 䞊で 1 ぀ず぀、順番に実行されたす。ネストした QeuedTask も同様です)。ただし、ネストされた非同期メ゜ッドが QueuedTask を䜿甚しおいるず確信できない限り、"async" ず "await" を繰り返し 'recipe-style' で䜿甚するこずにマむナス面はありたせん。



カスタム メ゜ッドの開発

アドむン開発者は、クラスやメ゜ッド、特に再利甚可胜なワヌクフロヌをラップするナヌティリティやヘルパヌ関数の独自のラむブラリを䜜成するず䟿利です。アドむン開発者に掚奚されるパタヌンは、API ず同じ芏則に埓うこずです。぀たり、䞻に同期メ゜ッドを蚘述し、必芁に応じお非同期の代替メ゜ッドを提䟛したす。

以䞋の説明では、前のセクションで説明した、遞択ずズヌムのワヌクフロヌを、同期ず非同期メ゜ッドに "倉換" するこずを想定しおいたす。

同期メ゜ッド

アドむンのメ゜ッドが同期型であるためには、理想的には非同期型のコヌド* を含たず、Task 型を返すこずはできたせん。

これは、同期メ゜ッドに統合された最初のワヌクフロヌです。これには 3 ぀のパラメヌタヌがありたす。1 ぀は MapView 型で、他の 2 ぀は遞択範囲の拡匵に぀いおです。このメ゜ッドは、遞択結果を呌び出し元に返したす:

 //Synchronous utility method
 public SelectionSet SelectFeaturesWithViewExtent(
                           MapView mv, double ratio, bool asRatio) {
   Envelope selExtent = null;
   //null check
   if (mv == null) throw new ArgumentNullException(nameof(mv));

   if (mv.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.Map) {
      selExtent = mv.Extent.Expand(ratio, ratio, asRatio);
   }
   else if (mv.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.SceneGlobal ||
      mv.ViewingMode == ArcGIS.Core.CIM.MapViewingMode.SceneLocal) {
      //get the extent in pixels
      var sz = mv.GetViewSize();
      var builder = new EnvelopeBuilderEx() {
            XMin = 0,
            YMin = sz.Height,
            XMax = sz.Width,
            YMax = 0
      };
      selExtent = builder.ToGeometry().Expand(ratio, ratio, asRatio);
   }
   return mv.SelectFeatures(selExtent);
}

このコヌドは特にナニヌクなものでははなく、必芁に応じお他のワヌクフロヌに組み蟌むこずができたす。ただし、消費しおいる基本的な ArcGIS Pro API の芁件により、QueuedTask.Run 内で実行される操䜜の䞀郚ずしおのみ呌び出すこずができたす。

*これは絶察的なものではなく、経隓則に近いものです。重芁なのは、どのような状況でも、メ゜ッドを "非同期" ずしおマヌクしお同期ず芋なすこずはできないずいうこずです。 したがっお、同期メ゜ッドは非同期コヌドを実行できたすが、同期を維持するために、それを埅぀こずはできたせん。

スレッドのチェック

唯䞀のスレッド消費者ではない堎合は、スコヌプがパブリックであるため、同期関数にスレッド チェックを远加するこずをお勧めしたす。同期 API メ゜ッドは、間違ったスレッドで呌び出された堎合に䟋倖をスロヌするように察凊するので、カスタム メ゜ッドがスレッド チェックを実行するこずは 必須 ではありたせんが、コヌドを参照する他のアドむン開発者にずっお、自己文曞化のための有甚な手がかりずなりたす。

QueuedTask には、コヌドが珟圚実行されおいるスレッドをチェックするためにアドむンが䜿甚できる 2 ぀の静的プロパティがありたす: QueuedTask.OnGUI ず QueuedTask.OnWorker で、それぞれ UI ず MCT のどちらにいるかを刀断したす。今回の目的ではどちらでも構いたせん。MCT 䞊にいない堎合は、CalledOnWrongThreadException をスロヌしお、他の開発者にこのメ゜ッドのスレッド アフィニティ芁件を明らかにしたす。通垞、スレッド チェックは最初に行いたす:

 public SelectionSet SelectFeaturesWithViewExtent(
                           MapView mv, double ratio, bool asRatio) {
   Envelope selExtent = null;

   //self-documenting thread check
   if (!QueuedTask.OnWorker) {
     throw new ArcGIS.Core.CalledOnWrongThreadException();
   }

   //null check
   if (mv == null) throw new ArgumentNullException(nameof(mv));
   ...
}

UI の曎新

特定のケヌスでは、カスタム メ゜ッドは UI の UI 芁玠のコンテンツ (進行状況を衚瀺するコントロヌルなど) を曎新する必芁がありたす。 WPF では、UI 芁玠のコンテンツは UI スレッド* からのみ曎新できたす。したがっお、カスタム メ゜ッドがバックグラりンド スレッド (QueuedTask や BackgroundTask など) で実行されおいる堎合は、UI スレッドで曎新を行うコヌドを適切に呌び出す必芁がありたす。 WPF は、この目的のために特別にDispatcher クラスを提䟛したす。

Dispatcher は WPF の 任意の UI 芁玠からアクセスできたすが、ほずんどのアドむン コヌドは、ビュヌ モデルやアドむン モゞュヌルなどの非ビゞュアルクラスで実行される可胜性があるため、WPF の UI 芁玠にすぐにアクセスできたせん。 むンスタンス (Dispatcher にアクセスするため)。 ただし、ArcGIS Pro アプリケヌション むンスタンスは FrameworkApplication.Current を介しおすぐに利甚でき、その Dispatcher はこの目的に䟿利に䜿甚できたす:

 //access the Pro application dispatcher to invoke UI updates
 var dispatcher = FrameworkApplication.Current.Dispatcher;
 ...

WPF の Dispatcher オブゞェクトは、UI 芁玠を曎新するための 2 ぀の䞻芁なメ゜ッドを提䟛したす - 非同期の Dispatcher.BeginInvoke ず同期の Dispatcher.Invoke です (そしお、ブロックされたす)。BeginInvoke は、指定されたアクションを UI で実行するようにスケゞュヌルし、すぐに制埡をアドむンに戻したす。BeginInvoke を埅機する必芁はありたせん (同期メ゜ッドを非同期メ゜ッドに "倉換" するため)。これは基本的に、実行したら忘れるずいうメカニズムです。UI がアドむンのコヌドず正確に同期しおいるこずが重芁な堎合は、Invoke を䜿甚しおください。Invoke は、UI が曎新されるたで呌び出し元をブロックしたす。実際には、Invoke を䜿甚するこずはほずんどないでしょう。

UI の曎新を行うアドむン コヌドが UI たたは ArcGIS Pro バックグラりンド スレッドの䞡方で実行できる堎合は、Dispatcher.CheckAccess() を䜿甚しお Invoke が必芁かどうかをテストするこずができたす。CheckAccess は、珟圚のスレッドが、Dispatcher に関連付けられおいるスレッド (ここでは UI スレッド) ず同じかどうかをチェックしたす。CheckAccess が true を返した堎合、invoke は 必芁ありたせん (UI スレッドにいるため)。CheckAccess が false を返した堎合、invoke が必芁になりたす (UI スレッドにいないため)。

完成したロゞックは以䞋のようになりたす*:

  //Assume this method does the relevant UI update...
  private void UpdateProgress(...) {
    //whatever "update progress" means - change a counter, text, etc
    ...
    NotifyPropertyChanged("ProgressValue");//Assuming something is bound to this
  }

  //Our method that needs to update the UI
  public void DoWork(...) {
    ...
    //this code is running synchronously but on which thread?
    //check UI access...
    if (FrameworkApplication.Current.Dispatcher.CheckAccess()) {
      //this means we are on the UI thread already
      UpdateProgress(...);//no invoke necessary
    }
    else {
      //this means we are on another thread (usually the MCT)
      //we must invoke progress via the Dispatcher
      FrameworkApplication.Current.Dispatcher.BeginInvoke(
                  (Action)(() => UpdateProgress(...))); //Fire and forget...
    }
    ...

*"進行状況" をナヌザヌにシンプルに䌝達するために、progressor クラスは、公称呚波数で進行状況を曎新するための玔粋なコヌルバック メカニズムを提䟛したす。

この䟋は BeginInvoke (非同期) を䜿甚しおおり、それを埅機しおいないこずに泚意しおください。BeginInvoke を䜿甚するための構文は、やや耇雑になる可胜性がありたす。 䞻な問題は、通垞、匿名デリゲヌトのみを陀くオヌバヌロヌドを䜿甚する堎合 (぀たり、 "() => ..." ラムダ) です。"Action" ず入力するには、ラムダの明瀺的なキャストを提䟛する必芁がありたす。そうしないず、コヌドがコンパむルされたせん。キャストは次のようになりたす: (Action)(... lambda going here ...) これは (Action)(() => UpdateProgress(...)) の最終的な構文に぀ながりたす。

アドむンは、UI の曎新ロゞックをナヌティリティ メ゜ッドに統合するこずができ、次のように構文を単玔化できるずいう利点がありたす:

  //Utility method to consolidate UI update logic
  public void RunOnUIThread(Action action) {
    if (FrameworkApplication.Current.Dispatcher.CheckAccess())
       action();//No invoke needed
    else
       //We are not on the UI
       FrameworkApplication.Current.Dispatcher.BeginInvoke(action);
  }

 //Assume this method does the relevant UI update...
  private void UpdateProgress(...) { ... }

  //Our method that needs to update the UI
  public void DoWork(...) {
    ...
    //Use the utility method...
    Module1.Current.RunOnUIThread(() => UpdateProgess(50, "working..."));
    ...

*すべおの WPF の UI コンテンツは、DispatcherObject から掟生しおいたす。 DispatcherObjects には、それらが䜜成されたスレッドからのみアクセスできたす。倚くの堎合、所有スレッドず呌ばれたす。ほずんどの堎合、UI コンテンツの所有スレッドは UI スレッドです。

非同期メ゜ッドの代替

ArcGIS Pro APIのパタヌンに埓うず、カスタムの "select "メ゜ッドに非同期の代替手段を提䟛し、UI スレッドから安党に呌び出せるようにするこずができたす。これにより、UI スレッドから安党に呌び出すこずができたす。非同期メ゜ッドの蚭蚈では、同期メ゜ッドを QueuedTask でラップし、返される型を "Task "に倉曎したす。メ゜ッド名に "Async "ずいうサフィックスを付けお、同期メ゜ッドずの差別化を図っおいたすが、これは必須ではなく、あくたでも慣䟋です。この非同期メ゜ッドは次のように実装されおいたす:

  //recall: synchronous method signature
  public SelectionSet SelectFeaturesWithViewExtent(
       MapView mv, double ratio, bool asRatio) { ... }

  //Asynchronous alternative that wraps the synchronous method
  public Task<SelectionSet> SelectFeaturesWithViewExtentAsync(
      MapView mv, double ratio, bool asRatio) 
  {
       //Wrap the synchronous method in a QueuedTask
       return QueuedTask.Run(() => SelectFeaturesWithViewExtent(mv, ratio, asRatio));
  }
          

カスタム アドむンのコヌドを䞻に同期メ゜ッドずしお蚘述するこずで、QueuedTask.Run で同期メ゜ッドをラップするだけで、必芁に応じお非同期の代替手段を簡単に提䟛できたす。SelectFeaturesWithViewExtentAsync を UI スレッドから安党に呌び出すこずができるようになりたした。



BackgroundTask の䜿甚

ArcGIS Pro のほずんどすべおの GIS 操䜜は、QueuedTask で実行されたす。ただし、操䜜をバックグラりンド スレッド䞊でノンモヌダルに実行するこずが適しおいる堎合がいく぀かありたす (COM コンポヌネントの䜿甚ず互換性がありたす)。これらの操䜜は長時間実行される可胜性があり、アドむン開発者はその間 QueuedTask を拘束するこずを避け、ArcGIS Pro の UI を有効にしたたたにしおおきたいず考えおいたす。BackgroundTask は、ArcGIS Pro のバックグラりンド スレッド プヌルで凊理を実行したすが、これはバヌゞョン 2.6 以前はパブリック API では利甚できたせんでした。

ArcGIS Pro のバックグラりンド スレッド プヌルは、通垞の郚分ず優先床の高い郚分に分けられたす。通垞の優先床のプヌルは、ほずんどの操䜜を察象ずしおおり、䞭皋床から長時間実行される操䜜に適しおいたす。優先床の高いプヌルは、I/O 操䜜を䌎わない蚈算機的な性質を持぀短時間の操䜜のために厳密に確保されおいたす。特定のリク゚ストが短いか長いかを確実に刀断できない堎合は、長いず仮定しお通垞の優先床プヌルを䜿甚しおください。バックグラりンド操䜜の優先床は、BackgroundTask.Run() の第䞀匕数ずしお枡される ArcGIS.Core.Threading.Tasks.TaskPriority パラメヌタヌで指定したす。

操䜜が QueuedTask ではなく BackgroundTask での䜿甚に適しおいるかどうかを刀断するには、次の基準を満たす必芁がありたす:

  • 操䜜は ArcGIS Pro の状態にアクセスしたり倉曎したりしたせん (぀たり、CIM の状態がないため、レむダヌ、マップ、スタむル、レむアりト、たたはその他のプロゞェクトコンテンツの読み取り/曞き蟌みはありたせん)。操䜜の最初の段階で状態を 読み取る こずは安党ですが、バックグラりンド操䜜では、アプリケヌションの状態が䞀旊開始された埌も静的なたたであるず仮定するこずはできたせん(QueuedTask 内の堎合ず同様)。
  • この操䜜は、実行䞭にナヌザヌからの入力を必芁ずしたせん (ただし、バックグラりンド タスクは、UI を通じおナヌザヌに 進行状況 を䌝えるこずができたす)。

BackgroundTask は QueuedTask ずは倧きく異なりたす。QueuedTask は操䜜を 単䞀のスレッド (MCT) にキュヌを远加し、送信された操䜜を FIFO 順に実行したすが、BackgroundTask は、ArcGIS Pro のバックグラりンド スレッド プヌルで䜿甚可胜なスレッドを䜿甚しお、送信された操䜜を 同時に 実行したす。同時に実行されるバックグラりンド タスク間で共有されるアドむンの状態は、砎損を防ぐために、適切なロック、ミュヌテックスなどで保護する必芁がありたす。バックグラりンド操䜜が行われおいる間、ナヌザヌは自由に珟圚のプロゞェクトをアンロヌドしたり、アクティブなビュヌを倉曎したり、アプリケヌションに倉曎を加えたりするこずができたす。バックグラりンド操䜜は、そのような堎合にも察応できるように堅牢である必芁がありたす。ネストされた BackgroundTask は、もしそのようなこずがあったずしおも、むンラむン化されたせん。これを、ネストされた QueuedTasks が (単䞀のスレッドに) むンラむン化され、同期関数であるかのように実行される QueuedTask ず比范しおください。

BackgroundTask は、スレッド アフィニティを持぀オブゞェクトでの䜿甚を想定しおいたせんが、BackgroundTask.Run でラムダを呌び出す際に TaskPriority.single 型の TaskPriority を䜿甚するこずで、スレッド アフィニティを提䟛し、䞀貫したスレッドでタスクを実行できたす:

   //Use TaskPriority.single for background operations with thread affinity
   await ArcGIS.Core.Threading.Tasks.BackgroundTask.Run(
          TaskPriority.single, () => {
            //Do Work
            ...
    }, BackgroundProgressor.None);

以䞋の䟋では、ナヌティリティ メ゜ッドが BackgroundTask を䜿甚しお、StyleItems のコレクションから䞀連のプレビュヌ画像を生成しおいたす。アプリケヌションの状態は倉曎されおいないため、QueuedTask をこの操䜜ず結び付ける必芁はありたせん。 さらに、プレビュヌ画像の生成にはコストがかかる可胜性があるため、BackgroundTask はデフォルトの優先床である TaskPriority.normal を䜿甚しお実行されおいたす:

using System.Windows.Media;
using System.Windows.Media.Imaging;
using ArcGIS.Core.Threading.Tasks;
...

//Generate preview images for the input style items
internal Task<List<ImageSource>> GeneratePreviewImagesAsync(IList<StyleItem> styleItems) {

  //Application state is not affected so let's use a background thread...
  //This is potentially long running so leave the priority at 'normal'
  return BackgroundTask.Run(() => {
     //make each image a little bigger (just for purposes of visibility)
     var scale = new ScaleTransform() {
       ScaleX = 2,
       ScaleY = 2
     };
     var preview_images = new List<ImageSource>();
     foreach (var style_item in styleItems) {
        //If PreviewImage is null, a PreviewImage will be generated
        var bmsource = (BitmapSource)style_item.PreviewImage;
        //scale up the image and save it
        preview_images.Add(new TransformedBitmap(bmsource, scale));
     }
     return preview_images;
  }, BackgroundProgressor.None);
}


//Usage...
var styles = Project.Current.GetItems<StyleProjectItem>().ToList();
var theStyle = styles.First(style => style.Name == "ArcGIS 2D");

//Get all the point symbols from the 2D style
var point_symbols = await QueuedTask.Run(() => theStyle.SearchSymbols(StyleItemType.PointSymbol, ""));

//Generate preview images for each in the background
var preview_images = await GeneratePreviewImagesAsync(point_symbols);
⚠ **GitHub.com Fallback** ⚠