-----------------------------------

Acquista i software ArcGIS tramite Studio A&T srl, rivenditore autorizzato dei prodotti Esri.

I migliori software GIS, il miglior supporto tecnico!

I migliori software GIS, il miglior supporto tecnico!
Azienda operante nel settore GIS dal 2001, specializzata nell’utilizzo della tecnologia ArcGIS e aderente ai programmi Esri Italia Business Network ed Esri Partner Network

-----------------------------------



Visualizzazione post con etichetta Multithreading. Mostra tutti i post
Visualizzazione post con etichetta Multithreading. Mostra tutti i post

sabato 10 ottobre 2015

ArcGIS Pro: Nun te regghe Queued(Task)

ArcGIS Pro si differenzia nettamente dalle applicazioni ArcGIS desktop esistenti, poichè è sviluppato secondo un'architettura multithread per sfruttare le moderne CPU/GPU con più core di esecuzione. Per lo sviluppatore di add-in che vuole estendere le funzionalità di ArcGIS Pro, questo significa modificare il modello di programmazione e familiarizzare, in un primo momento, con alcuni nuovi concetti che possono apparire bizzarri. Ovviamente, come con qualsiasi novità, lavorare con questi modelli diventerà gradualmente sempre più facile, ed i benefici del multithreading saranno sempre più evidenti.
Sfide che affronterai nella programmazione multithreading:
Le seguenti quattro differenze fondamentali distinguono le applicazioni multithread tra cui ArcGIS Pro, da una classica applicazione a singolo thread singolo:
  • Per garantire un’esperienza d’uso (UX) responsiva, il thread dell'interfaccia grafica utente (GUI) deve essere in grado di ricevere l'input da parte dell'utente e produrre output senza intoppi e senza interruzioni. Ciò significa che le esecuzioni delle azioni programmate devono essere eseguite in modo asincrono su thread di lavoro separati; il thread della GUI non dovrebbe mai svolgere un lavoro di qualsiasi tipo che blocchi nell’attesa. Ciò è in contrasto con le esistenti applicazioni ArcGIS desktop dove la maggior parte del lavoro viene eseguito direttamente sul singolo thread della GUI.
  • Mentre il lavoro è in esecuzione sui thread in background, agli utenti deve essere presentata un’interfaccia utente logicamente coerente e informativa. Comandi, strumenti, e altre parti dell'interfaccia utente devono essere attivate o disattivate opportunamente basandosi su quali operazioni sono in esecuzione, e dovrebbe essere fornito un adeguato feedback. Se un'operazione di lunga esecuzione è logicamente annullabile, dovrebbe essere fornita la possibilità di annullarla.
  • Operazioni che possono generare conflitto non dovrebbero essere eseguite simultaneamente e devono sempre essere eseguite in una sequenza logica appropriata. Ad esempio, le operazioni su una mappa non possono essere eseguite mentre il progetto che contiene la mappa è ancora in fase di caricamento ed un insieme di feature selezionate non può essere cancellato finché la selezione stessa non è stata completata. La maggior parte delle operazioni avviate tramite l'interazione dell'utente sono logicamente dipendenti dall’ordine e dovrebbero essere eseguite in serie.
  • Bisogna fare attenzione a garantire che l'accesso allo stato volatile, cioè l'accesso alle variabili all'interno del programma, sia correttamente sincronizzato quando tale stato è condiviso tra i thread. Ad esempio, se un oggetto collection è condiviso tra un thread di lavoro e il thread della GUI, entrambi i thread devono essere coordinati per l’accesso alla collection in modo che un thread non legga gli elementi della collection, mentre un altro thread simultaneamente sta aggiungendo o rimuovendo elementi. Questo tipo di protezione è comune a tutti i tipi di programmazione multithreaded e viene normalmente realizzato utilizzando un lock. In un'applicazione in cui più parti indipendenti possono estendere il comportamento dell'applicazione, le operazioni di coordinamento possono diventare complesse e fuori controllo senza un comune framework che orchestra i vari componenti a lavorare insieme.


Il modello di threading interno ad ArcGIS Pro

Gli ingegneri di Esri hanno posto la massima priorità per rendere ArcGIS Pro facile da programmare il più possibile nella nuova architettura multithread. A tal fine, ArcGIS Pro incorpora le più recenti funzionalità della programmazione asincrona fornita da Microsoft insieme a nuove infrastrutture threading specifiche dell'applicazione su misura per ridurre la complessità del codice.
Nella maggior parte dei casi, gli sviluppatori di add-in dovrebbero solo aver bisogno di trattare con due thread: il thread dell'interfaccia utente e un thread di lavoro specializzato forniti dall'applicazione.
Internamente, ArcGIS Pro utilizza un gran numero di thread per vari scopi, tra cui la rasterizzazione, il rendering dei grafici, il caricamento dei dati e la selezione degli algoritmi di geoprocessing che sfruttano il parallelismo per velocizzare il calcolo. Mantenere tutte queste attività senza blocchi e senza conflitti richiede una notevole attività di coordinamento e di complessità associata; per questo motivo, questi thread sono completamente interni e isolati agli sviluppatori nell'ambito delle implementazioni con l’SDK pubblico. Quando viene chiamato un metodo nella API pubblica, l'implementazione interna può, quando applicabile, dividere l’operazione e delegare parti ad uno o più di questi thread interni specializzati, o accodare le operazioni che alla fine vengono eseguite all'interno di un processo esterno o un servizio web .



Attività (Tasks) e il modello asincrono basato su attività (TAP)

I metodi all'interno ArcGIS Pro SDK rientrano in tre categorie:
- Metodi asincroni che possono essere chiamati in qualsiasi thread. Metodi di questo tipo sono nominati utilizzando il suffisso Async e di solito restituiscono Tasks. In alcuni casi, può essere fornita sia la versione sincrona che asincrona del metodo.
- Metodi sincroni che dovrebbero essere chiamati solo sul thread di lavoro. Metodi di questo tipo sono segnalati nella guida in linea delle API e un tip sul codice appariranno quando siamo col cursore sul metodo.
- Metodi sincroni che dovrebbero essere chiamati solo sul thread della GUI. Questi tipi di metodi sono di solito associati con WPF.
Se un metodo su un particolare oggetto viene chiamato sul thread sbagliato, la chiamata genera un'eccezione di tipo ArcGIS.Core.CalledOnWrongThreadException. Se non sei sicuro su un particolare caso, è possibile consultare la guida del componente SDK o l’help fornito da Microsoft per determinare se un particolare metodo o una proprietà ha delle restrizioni.
All'interno dell’SDK - in particolare all'interno del namespace ArcGIS.Core – il thread di lavoro associa metodi e proprietà che tendono ad essere a grana fine. Per ridurre il tempo associato con la schedulazione e la commutazione di contesto, questi metodi sono sincroni e devono essere utilizzati nel codice usando task.
Il .NET Task Parallel Library di Microsoft (TPL) e il modello di programmazione associata noto come il modello asincrono basato su attività (TAP) semplificano la creazione di codice asincrono all'interno di un'applicazione multithread. La classe Task viene utilizzata per rappresentare un'operazione eseguita in modo asincrono.
Nel seguente esempio, il metodo PrintReportAsync viene richiamato e restituisce immediatamente un oggetto Task al chiamante. Nel frattempo, la funzione di stampa continua l’esecuzione in background su un altro thread.

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      Task t = PrintReportAsync ("HP1");
      // Attendo che il task abbia finito.
      t.Wait ();
      MessageBox.Show ("Il Report è pronto!");
    }

Questo esempio vuole mostrare un messaggio quando la stampa è completata e utilizza il metodo Wait sull'oggetto Task restituito per sospendere il thread chiamante fino a quando il task ha completato il suo compito.
Questo approccio presenta due grandi inconvenienti: in primo luogo il thread chiamante non può fare altro mentre è in attesa; in realtà è meno efficiente che semplicemente chiamare una versione sincrona della funzione di stampa. In secondo luogo, poiché il thread chiamante è un thread della GUI, l'interfaccia utente risulta ‘congelata’. Un thread sospeso, ovviamente, non è in grado di elaborare l'input dell'utente, aggiornare elementi grafici o fare qualsiasi altra cosa. Per queste ragioni, non si dovrebbe mai usare il metodo Wait su un thread della GUI.
Fortunatamente, .NET introduce le funzionalità al linguaggio async e await. Il modificatore async segna il metodo in modo che il compilatore sappia che il metodo è asincrono e usi l'operatore await. L'operatore await è molto utile in quanto è utilizzato per chiamare i metodi in modo asincrono e dopo per forzare il thread chiamante a tornare automaticamente alla riga successiva e continuare l'esecuzione una volta che l'operazione asincrona è stata completata. Il thread chiamante - normalmente il thread della GUI - non è bloccato ed è libero di adottare altre azioni mentre il task sul thread di lavoro è ancora in esecuzione.
Si noti che possiamo modificare ora l'obiettivo originale con poche variazioni, ma in questo caso l'interfaccia utente non si blocca.

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
      Task t = PrintReportAsync ("HP1");
      // Attendere (senza blocco) fino a quando il task è completato.
      await t;
      // riparte da qui quando il task è completato.
      MessageBox.Show ("Il report è pronto!");
    }

L’utilizzo di Run

Quando una funzione asincrona non è disponibile, è possibile scrivere facilmente le proprie funzioni wrapper che eseguono internamente uno o più metodi sincroni. L'esempio seguente utilizza il metodo statico Run per accodare l'esecuzione della funzione WorkFunc ad una thread casuale nel pool di thread dei Task. Si noti che il metodo click restituisce immediatamente il controllo al chiamante, mentre il WorkFunc continua a eseguire sul thread di lavoro.

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      Task t = Task.Run ((Azione) WorkFunc);
    }
    private void WorkFunc ()
    {
      // my work
    }

Invece di utilizzare una funzione separata, può essere impiegata una funzione anonima chiamata anche lambda. Utilizzando le lambda, mantieniamo il codice della funzione di lavoro nella funzione stessa consentendoci di utilizzare gli argomenti e le variabili locali all'interno della lambda come se fossero parte della funzione stessa.

    private void Button_Click(object sender, RoutedEventArgs e)
    {
      int steps = GetSteps();
      Task t = Task.Run (() =>
      {
        // Posso utilizzare la variabile steps qui anche se mi trovo in una
        // diversa funzione in esecuzione su un diverso thread!
        // my work
      });
    }

I Task possono anche essere parametrizzati per restituire un tipo particolare, come risultato di un calcolo della lambda.

      Task<double> t = Task.Run<double>(() =>
      {
        double risultato;
        // Calcolo della variabile risultato …
        return risultato;
      });

L'operatore await può essere utilizzato anche in linea per ottenere il risultato della funzione asincrona e senza doverlo estrarre dal Task restituito.

    private async void Button_Click(object sender, RoutedEventArgs e)
    {
      double computedValue = await Task.Run <double>(() =>
      {
        double risultato;
        // Calcolo della variabile risultato ...
        return risultato;
      });
      // L’esecuzione riprende automaticamente qui quando il task sopra ha  completato!
      MessageBox.Show (String.Format ("Il risultato era {0}", computedValue.ToString ()));
    }

C'è un piccolo ‘carico’ associato ad await, quindi è sempre più efficiente chiamare più metodi sincroni all'interno della propria lambda che chiamare molte funzioni asincrone utilizzando await. Questo è particolarmente vero quando si scrive codice in un loop, dove il costo di utilizzo await attraverso centinaia o migliaia di iterazioni diventa sostanziale.


L’utilizzo di QueuedTask

Mentre i Task sono un appuntamento fisso all'interno di qualsiasi codice dell’add-in, i task devono essere forniti in ArcGIS Pro in modo diverso dal tradizionale TAP. Il framework fornisce una schedulazione personalizzata del Task che dovrebbe essere utilizzato quando fornisci Task che effettuano chiamate ai metodi sincroni di ArcGIS Pro SDK. Invece di chiamare Task.Run, gli sviluppatori di add-in devono chiamare QueuedTask.Run.

      Task t = QueuedTask.Run (() =>
      {
        // Chiama metodi SDK sincroni
      });

La classe QueuedTask viene utilizzata al posto della classe Task per i seguenti motivi:

Controllo della concorrenza e delle code

Quando i Task vengono inviati utilizzando Task.Run, il Task associato sarà in esecuzione su un thread a caso nel pool di thread gestito ogni volta che viene chiamato. Se una chiamata successiva a Task.Run viene fatta da qualsiasi altra parte dell'applicazione, il nuovo Task inizierà a funzionare immediatamente su un altro thread mentre il primo Task è ancora in esecuzione sul primo thread. Tornando alla lista delle sfide da affrontare nel codice multithread, dovrebbe essere ovvio che l'esecuzione delle operazioni non organizzate e concorrenti rischia di far andare in crash l’applicazione o di corrompere lo stato dell'applicazione. Il comportamento di accodamento QueuedTask.Run garantisce il corretto ordine delle chiamate e riduce il rischio di conflitti. Ricordate che il parallelismo all'interno di ArcGIS Pro si realizza internamente; questo semplifica il modello di programmazione pubblica e riduce notevolmente la probabilità di conflitti.

Affinità e stato

Per motivi di prestazioni, ArcGIS Pro mantiene un notevole stato su specifici thread e, in molti casi, utilizza oggetti che hanno affinità di thread. Affinità di thread significa che un oggetto è legato ad un particolare thread e non deve interagire con qualsiasi thread ma con il thread col quale ha affinità. Vincoli di affinità sono comuni in sistemi operativi e componenti, tra cui connessioni a database, windows, controlli, code di input, timer, WPF Bitmap e server COM. In WPF, per esempio, chiamate a metodi su un qualsiasi oggetto derivato dalla classe WPF DependencyObject si tradurrà in un'eccezione se la chiamata viene effettuata da un thread da dove l'oggetto non è stato creato.
Thread nel pool di thread gestiti sono anche incompatibili con la maggior parte dei componenti COM, per cui non si dovrebbe tentare di utilizzare Task.Run con codice che potrebbe eseguire direttamente o indirettamente, componenti COM.

Integrazione dell’applicazione

Quando i Task vengono eseguiti utilizzando QueuedTask.Run, essi vengono integrati automaticamente con varie funzionalità all'interno dell'applicazione come segue:
  • L’estensione della Progress/Cancellation del framework, in cui la progress include la finestra di dialogo con lo stato di avanzamento programmabile, viene visualizzata e nascosta automaticamente e lo stato cancellazione è correttamente comunicato tra le parti interessate dell’applicazione.
  • Lo stato occupato del sistema dell’applicazione dove gli elementi dell'interfaccia utente come pulsanti e strumenti sono attivati e disattivati automaticamente quando i Task sono in esecuzione. Esecuzione dei task possono essere coordinati anche con fasi critiche quali la creazione di viste e chiusura dell'applicazione.
  • La coda dei Task è scritta nelle strutture di diagnostica del framework, quando abilitate. Questo consente agli sviluppatori di monitorare la sequenza di esecuzione dei task, i task in esecuzione e la durata dell'esecuzione. Questo tipo di informazione è preziosa per il debugging e l'analisi delle prestazioni.

Casi in cui è accettabile l'utilizzo di Task.Run

Ci sono casi in cui l'uso di Task.Run è accettabile, ad esempio quando si eseguono operazioni in background indipendenti costituite interamente da codice in modo gestito a condizione che i particolari componenti gestiti in uso non abbiano affinità di thread. Lo sviluppatore si assume la piena responsabilità della gestione della cancellazione, della visualizzazione dello stato di avanzamento, dell'abilitazione/disabilitazione dell'interfaccia utente in modo appropriato e del coordinamento delle operazioni e della gestione della logica dei conflitti.

Progress e cancellation

I metodi asincroni possono talvolta accettare un argomento Progressor, un oggetto che viene utilizzato dal chiamante per configurare le impostazioni della finestra di dialogo per lo stato di avanzamento e annullamento e di coordinare la comunicazione tra il chiamante e il chiamato. I metodi asincroni che non sono annullabili prendono una classe Progressor, mentre i metodi annullabili prendono una classe CancelableProgressor.
Gli oggetti Progressor seguono il modello stabilito dal CancelationToken di Microsoft e non possono essere creati direttamente; invece, lo sviluppatore deve creare un ProgressorSource o CancelableProgressorSource.
Gli oggetti "source" consentono di configurare come il progressor gestirà il progress senza esporre queste impostazioni al codice esterno, che potrebbero accedere al Progressor. L'oggetto ProgressorSource espone i seguenti costruttori:

  public ProgressorSource (Action<Progressor> callback)
  public ProgressorSource (ProgressDialog progDlg)
  public ProgressorSource (string message, bool delayedShow = false)

Il primo override prende un delegato che sarà chiamato, a intervalli regolari, mentre il Task è in esecuzione. Questa opzione è appropriata quando si desidera fornire un feedback specializzato durante l'esecuzione del Task.
Il secondo override prende una finestra dialogo di avanzamento, oggetto costruito separatamente. Se non è già visualizzato, il progressor mostrerà automaticamente questa finestra di dialogo di avanzamento quando il Task inizia l’esecuzione e si nasconderà automaticamente al completamento del Task. Se la finestra di dialogo è già visibile, il progressor aggiornerà il contenuto della finestra di dialogo durante l'esecuzione e sarà compito dello sviluppatore nascondere la finestra di dialogo di avanzamento al momento opportuno. Questa opzione è appropriata quando si desidera controllare manualmente la visibilità della finestra dello stato di avanzamento, ad esempio quando è necessario mantenere la finestra dello stato di avanzamento tra diverse attività separate.
Il terzo override creerà automaticamente e mostrerà una finestra di dialogo di avanzamento quando il Task inizia l'esecuzione e lo nasconde al termine del completamento del Task. Il parametro delayedShow controlla se la finestra di dialogo di avanzamento deve essere mostrata immediatamente o ritardare la sua apparizione per consentire operazioni rapide da completare ed evitare di apparire, se non necessario. Se si prevede che il Task sia rapido nel completamento dell’esecuzione, impostare questo parametro a true. Se si prevede che il Task abbia bisogno di più di uno o due secondi per completare l’operazione, impostare delayedShow a false in modo che la finestra di dialogo di avanzamento appaia immediatamente per trasmettere responsività.
CancelableProgressors richiedono un ulteriore argomento che specifica cosa dovrebbe visualizzare il messaggio di annullamento. Verrà visualizzato il messaggio di annullamento, non appena l'utente fa clic sul pulsante Annulla nella finestra di dialogo.

  public CancelableProgressorSource(Action <CancelableProgressor> callback)
  public CancelableProgressorSource(ProgressDialog progDlg)
  public CancelableProgressorSource(string message, string cancelMessage, bool delayedShow = false)

Esempio di implementazione del metodo utilizzando cancellation

Lo specializzato CancelableProgressor espone una proprietà CancellationToken che può essere utilizzata per comunicare l’annullamento. All'interno dell'implementazione del metodo, il codice in esecuzione nel loop dovrebbe controllare la proprietà IsCancellationRequested e uscire dal metodo gettando l’eccezione OperationCanceledException (che riconosce la richiesta di cancellazione) come illustrato di seguito:

public Task<long> CalcFactorialAsync(int x, CancelableProgressor progressor)
{
  return QueuedTask.Run<long>(() =>
  {
    long result = 1;
    for (int i = 1; i < x; ++i)
    {
      if (progressor.CancellationToken.IsCancellationRequested)
        throw new OperationCanceledException();
      result *= i;
    }
    return result;
  });
}

Utilizzare con i metodi asincroni la finestra di dialogo integrata

Se il Progressor è stato configurato per mostrare lo stato di avanzamento, il Task in esecuzione può aggiornare le informazioni visualizzate nella finestra di avanzamento utilizzando il progressor (entrambe Progressor e CancelableProgressor supportano finestre di dialogo di avanzamento):

public Task<long> CalcFactorialAsync(int x, Progressor progressor)
{
  return QueuedTask.Run<long>(() =>
  {
    long result = 1;
    for (int i = 1; i < x; ++i)
    {
      progressor.Message = string.Format("Working on step:{0}", i);
      result *= i;
    }
    return result;
  }, progressor);
}

Complicazioni comuni

Ipotesi di stato costante

Si consideri il seguente esempio. Questa chiamata viene richiamata dal thread della GUI e l'intento è quello di eliminare lo specifico layer dalla mappa della vista attiva.

private Task DeleteSelectedLayerAsync(Layer layer)
{
  return QueuedTask.Run(() =>
  {
      MapView.Active.Map.RemoveLayer(layer);
  });
}

Anche se semplice in apparenza, questa funzione a volte può causare un'eccezione quando in uso all'interno dell'applicazione. L'errore qui è stato quello di pensare che lo stato del sistema rimanga statico attraverso i thread. Ci possono essere precedentemente delle operazioni in coda in esecuzione e queste devono essere completate prima che un'altra operazione possa iniziare l'esecuzione. Durante questo tempo, lo stato dell’applicazione può cambiare a causa dell’interazione con l'utente o il risultato di una operazioni ancora in esecuzione. In questo caso, l’oggetto attivo potrebbe diventare una tabella prima che la lambda effettivamente inizi l’esecuzione, nel qual caso la mappa sarà nulla determinando un'eccezione. L'approccio sicuro è quello di evitare "concatenamenti" di chiamate su variabili membro o variabili passate tra thread; utilizzare variabili locali come istantanee dello stato dell'applicazione quando il metodo è stato chiamato, dal momento che non cambieranno al loro interno.

private Task DeleteSelectedLayerAsync (Layer layer)
{
  // Prendere una "istantanea" della mappa sulla vista attiva.
  Map m = MapView.Active.Map;
  return QueuedTask.Run (() =>
  {
      m.RemoveLayer(layer);
  });
}

In un ambiente multithreading si dovrebbe gestire il codice allestendo una strategia difensiva. Si consideri un Task che modifica come un particolare layer debba essere simboleggiato. Se tale Task finisce in una coda dietro un altro Task che rimuove questo stesso layer dalla mappa, la seconda operazione è logicamente invalidata dalla prima. Per gestire questo caso correttamente, il secondo Task dovrebbe essere gestito per visualizzare un avviso o annullare l'operazione in modalità silente quando viene a sapere che il layer è stato eliminato.

Thread safe con associazione dati  in WPF

Per impostazione predefinita, i dati di collection associati a WPF devono essere modificati sul thread dove è stata creata l’associazione al controllo WPF. Questa limitazione diventa un problema quando si desidera riempire la collection da un thread di lavoro per produrre una buona esperienza per l’utente. Ad esempio, un elenco dei risultati di ricerca che viene riempito gradualmente man mano che vengono trovate corrispondenze, senza costringere l'utente ad attendere fino a quando l'intera ricerca è completata.
Per ovviare a questa limitazione, WPF fornisce una classe statica BindingOperations che permette di stabilire un'associazione tra un lock e una collection (ad esempio, ObservableCollection<T>). Tale associazione consente a collection associate di essere aggiornate dai thread esterni al thread principale della GUI, in modo coordinato senza generare la consueta eccezione.
BindingOperations.EnableCollectionSynchronization(Items, _lockObj);
Nell'esempio sopra, la variabile _lockObj - di tipo oggetto - è in genere istanziata quando viene creata la classe contenitore e servirà come lock per il coordinamento. Una volta che si chiama EnableCollectionSynchronization, WPF entrerà nello specificato lock quando c’è lettura o scrittura alla collection associata. Come proprietari della collection, si è obbligati a inserire il lock durante la lettura alla collection o scrittura della collection.
I wrapper ReadOnlyObservableCollection sono comunemente usati per forzare le sole letture semantiche sulle proprietà della collection osservabile. Per impostare correttamente la sincronizzazione con multithreading, è necessario chiamare EnableCollectionSynchronization sul wrapper, invece che sulla collection stessa, dal momento che è il wrapper al quale il WPF sarà associato effettivamente.

internal class HelloWorld
{
  private ObservableCollection<string> _items = new ObservableCollection<string>();
  private ReadOnlyObservableCollection<string> _itemsRO;
  private Object _lockObj = new Object();
  internal HelloWorld()
  {
    _itemsRO = new ReadOnlyObservableCollection<string>(_items);
    BindingOperations.EnableCollectionSynchronization(_itemsRO, _lockObj);
  }
//la proprietà pubblica utilizzata per il binding
public ReadOnlyObservableCollection<string> Items { get { return _itemsRO; } }
//all’interno della funzione di lavoro, il lock è inserito prima di modificare //la collection:
public void FillCollectionAsync()
{
  QueuedTask.Run(() =>
  {
    // letture e scritture dovrebbe essere fatte all’interno del lock
    lock (_lockObj)
    {
       _items.Add( GetData() );
    }
  });
}

Oggetti "Live" come proprietà

Si deve prestare attenzione quando si espongono oggetti – soprattutto collection - come proprietà pubbliche se la collezione è destinata a cambiare in un thread separato. Se qualcuno ottiene e trattiene tale proprietà e poi inizia l'enumerazione attraverso un thread A, un'eccezione può essere generata se il proprio codice modifica la collection sul thread B in quanto non vi è alcun lock di collaborazione tra essi. Distribuire in sola lettura istantanee della collection è più sicuro.

Eseguire codice sul thread della GUI

Ci sono casi in cui di tanto in tanto, mentre il codice viene eseguito su un thread di lavoro, si incontrano situazioni in cui è necessario chiedere input da parte dell'utente prima di procedere. Non si dovrebbe cercare di presentare una finestra direttamente dal thread di lavoro poiché le finestre hanno affinità di thread. Una finestra di dialogo creata sul thread di lavoro non si collega alla coda di input del thread della GUI e non rispetta lo z-order e la politica di focus stabilita dal thread GUI. In generale, è possibile eseguire codice sul thread della GUI da un thread di lavoro usando l'oggetto dispatcher dell'applicazione.
Questo può essere fatto in modo sincrono.

      FrameworkApplication.Current.Dispatcher.Invoke (() =>
      {
        // Fare qualcosa sul thread  della GUI
        System.Windows.MessageBox.Show ("Pronto!");
      });

O in modo asincrono:

      FrameworkApplication.Current.Dispatcher.BeginInvoke (() =>
      {
        // Fare qualcosa sul thread della GUI
        System.Windows.MessageBox.Show ("Pronto!");
      });

Si dovrebbe cercare di raccogliere le informazioni necessarie da parte dell'utente sul thread della GUI prima di eseguire il lavoro in modo da non dover utilizzare questa scorciatoia. Bloccando chiamate tra thread c’è rischio di deadlock e di trattenere operazioni in esecuzione sul thread di lavoro.

La gestione delle eccezioni in ambiente asincrono

Come le funzioni sincrone, le funzioni asincrone possono gettare eccezioni. Questo introduce un problema interessante da quando il chiamante fornisce il try/catch in un thread, e l'eccezione viene generata su un altro. Inoltre, il frame del chiamante non è solitamente ancora nello stack quando viene generata l'eccezione.
Tuttavia, .NET permette di utilizzare async/await con try/catch in modo che, se viene generata un'eccezione dal codice in esecuzione all'interno del Task, sarete in grado di catturare di nuovo da dove la funzione asincrona è stata chiamata. Si noti che la funzione asincrona deve restituire Task o Task<T> per eccezioni asincrone per essere adeguatamente rediretta (non void).

      try
      {
        var result = await PrintMapAsync ();
      }
      catch (Exception e)
      {
        // gestione eccezione.
      }

Se viene generata un'eccezione da parte del worker e non è stato fornito un try/catch intorno a dove si attende il worker, il runtime .NET si collegherà all'eccezione come eccezione inner, UnobservedException.
Eccezioni Unobserved solitamente compaiono solo quando l'oggetto eccezione viene raccolto dalla garbage collection di .NET, in nessun posto vicino a dove in realtà si è verificata l'eccezione. Se si ottiene uno di questi, esaminare l'eccezione interna per ottenere lo stack delle chiamate fallite. Nella finestra di controllo di VisualStudio, è possibile utilizzare la pseudo variabile $exception per esaminare il corrente oggetto eccezione.

Oggetti Freezable

WPF definisce un modello in cui determinati tipi di oggetti possono essere "congelati". Una volta che l'oggetto è congelato, modifiche non possono essere apportate all'oggetto senza che si generi un'eccezione. Oggetti freezable possono migliorare le prestazioni in alcune situazioni, e permettono anche di condividere l'oggetto tra i thread (vedi affinità di thread). Ad esempio, se un BitmapImage viene creato su un thread di lavoro, non è possibile utilizzarlo in seguito sul thread della GUI a meno di congelarlo prima.
Si consideri un caso comune in cui l'associazione venga utilizzata in combinazione con le immagini che sono state generate su un thread di lavoro. La classe di esempio VM qui sotto espone una proprietà chiamata Img:

public class VM: INotifyPropertyChanged
  {
    public BitmapImage Img {get {return _image; }}
    ...
  }

Questa proprietà restituisce un'istanza di BitmapImage (un oggetto Freezable), che è poi associata ad un pulsante in XAML:

<Button>
<Image Source = "{Binding Img}"> </ Immagine>
</ Button>

La bitmap sottostante viene periodicamente aggiornata sul thread di lavoro come segue; notare che verrà creata la bitmap sul thread di lavoro:

public Task Refresh ()
    {
      return QueuedTask.Run (() =>
      {
        var uri = GenerateThumbnail();
        Img = new BitmapImage (uri);
      });
    }

Nel processo di rendering dell'interfaccia utente, WPF tenterà di accedere alla proprietà bitmap dal thread della GUI ... ma questo si tradurrà in un'eccezione perché la bitmap è ancora "scongelata" e quindi ancorata al suo thread di lavoro principale. Questo problema può essere risolto semplicemente "congelando" la Bitmap dopo il suo l'aggiornamento.

var uri = GenerateThumbnail ();
Img = new BitmapImage (uri);
Img.Freeze ();

Nota: non tutte le classi che ereditano da System.Windows.Freezable possono essere congelate. Utilizzare la proprietà CanFreeze per verificare se l’oggetto può essere congelato.

sabato 2 maggio 2009

Multithreading con gli ArcObjects

Vi siete mai chiesti il motivo per il quale, ad esempio, quando create il wizard di una Windows Application, la funzione main viene marcata con l'attributo [STAThread]?

namespace WindowsFormsApplication2

{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Form1());
}
}
}






Troverete la risposta alle vostre domande consultando la seguente pagina del SDK .NET di ArcGIS Desktop/Engine.
Se vi può essere d'aiuto, qui sotto trovate una mia libera traduzione di questo documento. Spero anche che questo post possa favorire uno scambio di opinioni su un argomento certamente ostico.

Il multithreading consente ad una applicazione di effettuare più operazioni alla volta all'interno di singolo processo. In questo articolo vediamo cosa significa il multithreading nel contesto degli arcobjects e quali sono le regole che devono essere seguite per integrare il threading nelle applicazione arcobjects.
Il multithreading è normalmente utilizzato per migliorare la reattività delle applicazioni. Questa reattività può essere il risultato di reali prestazioni migliorate o la percezione di prestazioni migliorate. Usando multipli threads di esecuzione nel codice, puoi separare l'elaborazione dei dati e operazioni di input/output dalla gestione dell'interfaccia grafica (UI). Questo eviterà che lunghe operazioni di elaborazione dei dati riducano la reattività della tua UI.
I vantaggi delle prestazioni del multithreading hanno come aspetto negativo il fatto che si incrementa la complessità nel disegno e nel mantenimento del codice. I threads di una applicazione condividono lo stesso spazio di memoria, così devi garantire che l'accesso a strutture condivise sia sincronizzato per prevenire che l'applicazione entri in uno stato invalido, si blocchi o si chiuda improvvisamente. Questa sincronizzazione è spesso chiamata controllo concorrente.
Il controllo concorrente può essere ottenuto a due livelli: a livello di oggetto o di applicazione. Può essere ottenuto a livello di oggetto quando l'oggetto condiviso è thread safe, cioè l'oggetto forza tutti i threads che provano ad accedere ad esso, ad attendere fino a che il corrente thread abbia finito di modificare lo stato di quest'ultimo.
Il controllo concorrente può essere fatto a livello di applicazione acquisendo un esclusivo lock sull'oggetto condiviso, consentendo ad un thread alla volta di modificare lo stato dell'oggetto. Occorre fare attenzione ad un sovrautilizzo dei lock per proteggere i dati poichè può anche decrementare le prestazioni. Bilanciare le prestazioni e la protezione richiede un'attenta analisi della struttura dei dati ed un utilizzo di pattern adeguati per i tuoi threads.
Ci sono due cose da considerare quando sviluppi applicazioni multithreaded: thread safety e scalabilità. E' importante che tutti gli oggetti siano thread safe, ma avere oggetti thread safe non significa automaticamente che l'applicazione sia multithreaded o che il risultato dell'applicazione garantisca prestazioni migliori.
Il .NET Framework consente facilmente di generare threads nella tua applicazione; comunque, scrivere codice multithreaded ArcObjects dovrebbe essere fatto attentamente. La sottostante architettura degli ArcObjects è COM (Component Object Model). Per questa ragione, scrivere applicazioni multithreading ArcObjects richiede la conoscenza del NET. multithreading e del modello threading di COM.
Il multithreading non sempre farà in modo che il tuo codice sia più veloce; in molti casi aggiunge extra overhead e complessità che eventualmente potranno ridurre la velocità di esecuzione del tuo codice. Il multithreading dovrebbe solo essere usato quando la complessità aggiunta vale il costo. Una regola generale è che un task è adatto al multithreading se può essere diviso in differenti task indipendenti.
Tutti i componenti ArcObjects sono marcati come single threaded apartment (STA). Lo STA è un contenitore logico dove possiamo avere un singolo thread. Gli STA sono limitati ad un thread ognuno ma i COM non pongono limiti sul numero di STA per processo. Quando una chiamata di metodo entra in uno STA, viene trasferita nell'unico thread della STA. Di conseguenza, un oggetto in uno STA riceverà e processerà solo una chiamata di metodo alla volta ed ogni chiamata di metodo che riceve arriverà sullo stesso thread. I componenti ArcObjects sono thread safe, e gli sviluppatori possono usarli in ambienti multithreaded. Perchè le applicazioni ArcObjects siano eseguite efficientemente in un ambiente multithreaded, deve essere considerato il Threads in Isolation con l'apartment threading model degli ArcObjects. Questo modello lavora eliminando le comunicazioni cross-thread. Tutti i riferimenti ArcObjects con un thread soltanto comunicheranno con gli oggetti nello stesso thread.
Perchè questo modello funzioni, i singleton objects in ArcGis 9.x sono stati disegnati per essere singleton per thread e non singleton per processo. Lo svantaggio di ospitare multipli oggetti singleton in un processo è compensato dal guadagno in prestazioni evitando la comunicazione cross-thread che occorrerebbe se un singleton fosse creato in un thread e poi reso accessibile ad altri thread. Come sviluppatore nel sistema ArcGIS, tutti gli oggetti, inclusi quelli che tu scrivi, devono stare a questa regola per far lavorare il modello Threads in Isolation. Se stai creando un oggetto singleton come parte del tuo sviluppo, devi assicurarti che questi oggetti siano singleton per thread e non per processo.
Per riuscire ad utilizzare gli ArcObjects in un ambiente multithreaded, i programmatori devono seguire il modello Threads in Isolation mentre, scrivendo il codice multithreaded, in ogni modo occorre evitare errori nell'applicazione come situazioni di blocchi, prestazioni negative dovute a marshalling, e altri comportamenti inaspettati. Sebbene ci siano numerosi modi di implementare applicazioni multithreading, i seguenti sono alcuni dei più comuni scenari che gli sviluppatori incontrano.
Quando ti è richiesto di eseguire una lunga operazione, è conveniente eseguire l'operazione in un background thread mentre lasci libera l'applicazione di gestire altri task e lasci reattiva la UI. Degli esempi di queste operazioni includono iterazioni attraverso un FeatureCursor per caricare informazioni in un DataTable ed eseguire complesse operazioni topologiche mentre stai scrivendo i risultati in una nuova FeatureClass.
Per eseguire questo task, tieni in mente i seguenti punti:
- in accordo con il modello Thread in Isolation, non puoi condividere componenti ArcObjects tra threads. Invece puoi considerare il fatto che oggetti singleton sono per thread e, in un background thread, devi istanziare tutti i Factory richiesti per aprire FeatureClass, creare nuove FeatureClass, impostare riferimenti spaziali, e così via;
- tutta l'informazione passata al thread deve essere nella forma di tipo semplice o tipo gestito;
- nei casi in cui devi passare componenti ArcObjects dal thread main al worker thread, serializza l'oggetto in una string, passa la stringa al target thread, e deserializza l'oggetto indietro. Per esempio, puoi utilizzare XmlSerializerClass per serializzare l'oggetto, come una workspace connection properties (un IPropertySet), in una stringa; passa la stringa con la connection properties al worker thread; e deserializza la connection properties nel worker thread usando XmlSerializeClass. In questo modo, l'oggetto connection properties è creato nel background thread e le chiamate cross apartment sono evitate;
- mentre esegui il background thread, puoi riportare una task progress in una dialog box sullo stato di avanzamento dell'elaborazione (vedi più avanti come fare).
Il seguente esempio dimostra l'utilizzo di un background thread che è usato per ciclare mediante un FeatureCursor e popolare una DataTable che poi sarà usata più tardi dall'applicazione. Questo fa in modo che l'applicazione sia libera senza attendere che la tabella sia popolata.








        // Generate the thread that populates the locations table.
Thread t = new Thread(new ThreadStart(PopulateLocationsTableProc));
// Mark the thread as a single threaded apartment (STA) to efficiently run ArcObjects.
t.SetApartmentState(ApartmentState.STA);
// 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 code 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);
}
}










Dal sito MSDN: "In .NET Framework 2.0 i nuovi thread sono inizializzati come Apartment.MTA , se il loro stato di apartment non è stato impostato prima che siano partiti. Il thread della main application è inizializzato di default a Apartment.MTA. Non puoi impostare il thread dell'application main ad ApartmentState.STA impostando la proprietà Thread.ApartmentState nella prima linea di codice. Utilizza invece STAThreadAttribute". Come sviluppatore ArcObjects, questo significa che se la tua applicazione non è inizializzata come una single threaded application, il .NET framework creerà uno speciale singolo threaded apartment (STA) per tutti gli ArcObjects che sono marcati come STA. Questo farà sì che un thread vada a questo thread su ogni chiamata dall'applicazione agli ArcObjects. Questo forza i componenti ArcObjects ad una operazione di marshalling per ogni chiamata ed eventualmente potrebbe essere circa 50 volte più lento per una chiamata ad un componente COM. Fortunatamente, tutto ciò può essere evitato semplicemente marcando la funzione main come [STAThread].
Il seguente codice d'esempio marca una console application come STA:









namespace ConsoleApplication1
{ 
class Program 
{ 
[STAThread] 
static void Main(string[] args) 
{ 
// ... 
} 
}
} 









Thread pool threads sono background threads. Thread pooling ti abilita ad utilizzare threads in modo più efficiente fornendo alla tua applicazione un pool di worker threads che sono gestiti dal sistema. Il vantaggio di usare un thread pool piuttosto che creare un nuovo thread per ogni task è che la creazione e la distruzione sono negative, mentre il thread pool può dare prestazioni migliori e migliore stabilità al sistema. Comunque, per disegno tutti i ThreadPool threads sono in multithreaded apartment (MTA) e pertanto non sarebbero utilizzabili per eseguire ArcObjects, che sono single-threaded apartment. Per aggirare questo problema, abbiamo alcune opzioni. Una è implementare un dedicato thread ArcObjects che è marcato come STAThread e delegare ogni chiamata dall'MTA threads a questo dedicato thread ArcObjects. Un'altra soluzione è usare un'implementazione di una personalizzata STA thread pool, così come un array di threads marcati STAThread per eseguire ArcObjects. Il seguente esempio dimostra come usare un array di STAThread threads per prendere un sottoinsieme di un RasterDataset, usando un differente thread per estrarre un sottoinsieme di ogni banda raster.










public class TaskInfo
{
//defauld constructor
public 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)
{
try
{
//use rubber-band in order to getthe subset area from the user
IRubberBand2 rubberBand = new RubberEnvelopeClass();
IEnvelope envelope = (IEnvelope)rubberBand.TrackNew(m_pHookHelper.ActiveView.ScreenDisplay, null);
if (null == envelope  envelope.IsEmpty)
return;
// verify that there are layers in the map
if (m_pHookHelper.FocusMap.LayerCount == 0)
return;
// get the top raster layer
m_rasterLayer = null;
IEnumLayer layers = m_pHookHelper.FocusMap.get_Layers(null, false);
layers.Reset();
ILayer layer = layers.Next();
while (layer != null)
{
if (layer is IRasterLayer)
{
m_rasterLayer = (IRasterLayer)layer;
break;
}
layer = layers.Next();
}
if (m_rasterLayer == null)
{
MessageBox.Show("There is no raster layer in the map");
return;
}
// assert if there is no overlap between the user envelope and the top raster
envelope.Intersect(m_rasterLayer.AreaOfInterest);
if (envelope.IsEmpty)
{
MessageBox.Show("The delineated envelope does not intersect the topmost raster in the map");
return;
}
//query number of bands in the input raster
m_intBandCount = m_rasterLayer.BandCount;
IDataset dataset = (IDataset)m_rasterLayer;        
//get the workspace connection props since we'll need to connect it from each of the worker threads
IPropertySet connectionProps = dataset.Workspace.ConnectionProperties;
//get the workspace type
m_workSpaceType = dataset.Workspace.Type;
//serialize the connection properties into a string in order to avoid cross apartment calls
IXMLSerializer xmlSerializer = new XMLSerializerClass();
m_strInputRasterWorkspaceConnectionProps = xmlSerializer.SaveToString(connectionProps, null, null);       
//m_inputRasterName = dataset.Name;
m_inputRasterName = m_rasterLayer.FilePath;
//open a folder-browser in order to get the output location of the subset raster
FolderBrowserDialog folderDlg = new FolderBrowserDialog();
folderDlg.Description = "Select output folder for the subset raster";
folderDlg.ShowNewFolderButton = true;
if (DialogResult.OK != folderDlg.ShowDialog())
return;
//get the output folder that the user had chosen
m_outputRasterPath = folderDlg.SelectedPath;
//get the input raster layer name (will be used in order to create the output raster)
string inputRasterLayerName = m_rasterLayer.Name;
if (inputRasterLayerName.IndexOf(".") != -1)
inputRasterLayerName = inputRasterLayerName.Substring(0, inputRasterLayerName.IndexOf("."));
//set the output format
m_outputFormat = FormatType.IMG;
//create the output raster dataset
CreateRasterDataset(m_rasterLayer, m_outputRasterPath, inputRasterLayerName + "_Subset.img", envelope);
//show the output progress form
m_progressDlg = new frmProgress();
//force the control creation in order to make sure that the form has a handle
m_progressDlg.CreateControl();
//set the statusbar message of the progress dialog
m_progressDlg.SetStatusBarMessage("Subsetting raster " + m_rasterLayer.Name);
//show the progress dialog
m_progressDlg.Show();
//run the subset thread. The subset process uses WaitHandle in order to
//wait on the subsetting threads to finish their task. In order to use WaitHandle.WaitAll()
//the call must be made from a MTA. The current command is STA and therefore a thread must be created 
//in order to allow this(.NET threads gets created as MTA by default).
Thread t = new Thread(new ThreadStart(SubsetProc));
t.Start();
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}



















/// <summary>
/// main subset method
/// </summary>
/// <remarks>Please note that this sample does not copy colormap
/// in case where it is present. The outcome will be black and white</remarks>
private void SubsetProc()
{
try
{
m_lockObject = new object();
//set the message on the progress dialog statusbar  
m_progressDlg.SetStatusBarMessage("starting subset process");
//create ManualResetEvent in order to notify the main threads that all threads 
//are done with their task
ManualResetEvent[] doneEvents = new ManualResetEvent[m_intBandCount];
//create the subset threads
Thread[] threadTask = new Thread[m_intBandCount];
//create a thread for each of the bands of the input raster
//each task will subset a different raster-band. The information required for the 
//subsetting the raster-band will be passed to the task by the user-defined 
//class TaskInfo
for (int i = 0; i < m_intBandCount; i++)
{
//create the ManualResetEvent flad for the task in order to signal the waiting thread
//that the task had been completed
doneEvents[i] = new ManualResetEvent(false);
//create the Task-Information for the thread and pass in the relevant information
//Pleas note that all the information is passed as simple types
TaskInfo ti = new TaskInfo(i, doneEvents[i]);
ti.WorkspaceType = m_workSpaceType;
ti.Rows = m_intRows;
ti.Columns = m_intCols;
ti.I0 = m_i0;
ti.J0 = m_j0;
ti.InputRasterName = m_inputRasterName;
ti.OutputRasterPath = m_outputRasterPath;
ti.OutputRasterName = m_outputRasterName;
ti.InputRasterWSConnectionProps = m_strInputRasterWorkspaceConnectionProps;
// assign the subsetting thread for the rasterband.
threadTask[i] = new Thread(new ParameterizedThreadStart(SubsetRasterBand));
// Note the STA apartment which 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);
}
//Sets the state of the event to signaled, thus allowing one or more of the waiting threads to proceed
m_autoEvent.Set();
// Wait for all threads to complete their task...
WaitHandle.WaitAll(doneEvents);
// verify that all tasks are done and that the memory is cleaned
for (int i = 0; i < m_intBandCount; i++)
{
threadTask[i].Join();
}
threadTask = null;
// spin a new thread in order to claculate statistics and pyramid layers
Thread additionalDataTask = new Thread(new ParameterizedThreadStart(CalculateStatsAndPyramids));
additionalDataTask.SetApartmentState(ApartmentState.STA);
additionalDataTask.Name = "calc_aux_data";
additionalDataTask.Start(System.IO.Path.Combine(m_outputRasterPath, m_outputRasterName));       
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}


















/// <summary>
/// subset task method
/// </summary>
/// <param name="state"></param>
private void SubsetRasterBand(object state)
{
try
{
System.Diagnostics.Trace.WriteLine(Thread.CurrentThread.GetApartmentState());
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo)state;
//set the message on the progress dialog statusbar
m_progressDlg.SetStatusBarMessage("Copy RasterBand #" + (ti.BandID + 1).ToString());
IRasterDataset rasterataset = OpenRasterDataset(ti);
//cast the input raster bandCollection
IRasterBandCollection rasterBaneCollection = (IRasterBandCollection)rasterataset;
//get the raster band from which to copy the data
IRasterBand rasterBand = rasterBaneCollection.Item(ti.BandID);
IRawPixels rawPixels = (IRawPixels)rasterBand;
//open the newly created raster dataset
IRasterDataset newRasterDataset = OpenOutputRasterDataset(ti.OutputRasterPath, ti.OutputRasterName);
//get the raster-band into which data will be copied
IRasterBandCollection newRasterBandCollection = (IRasterBandCollection)newRasterDataset;
IRasterBand newRasterBand = newRasterBandCollection.Item(ti.BandID);
IRawPixels newRasterPixels = (IRawPixels)newRasterBand;
//set the size of the pixelBlock
int pixBlockSizeX = Math.Min(128, m_intCols);
int pixBlockSizeY = Math.Min(128, m_intRows);
//calculate the progressbar MaxValue
int hBlocks = Convert.ToInt32(m_intCols / pixBlockSizeX + 0.5);
int vBlocks = Convert.ToInt32(m_intRows / pixBlockSizeY + 0.5);
//set the maximum value of the progress controls of the progress dialog
if (ti.BandID == 0)
m_progressDlg.SetProgressbarMaximum(hBlocks * vBlocks);
//set the upper left pixel from which the subset starts
IPnt pnt = new PntClass();
pnt.SetCoords(Convert.ToDouble(pixBlockSizeX), Convert.ToDouble(pixBlockSizeY));
//instantiate the Top-Left-Corner to be used with the pixel-block for the copying process
IPnt tlc = new PntClass();
Monitor.Enter(m_lockObject);
//create the pixelBlock for the copying process
IPixelBlock pixelBlock = rawPixels.CreatePixelBlock(pnt);
Monitor.Exit(m_lockObject);
int p = 0;
int q = 0;
//read the raster band info from the input raster-band and write it to the new raster-band
for (int j = ti.J0; j < (ti.J0 + m_intRows); j += pixBlockSizeY)
{
for (int i = ti.I0; i < (ti.I0 + m_intCols); i += pixBlockSizeX)
{
//set the upper-left corner coordinate
tlc.SetCoords(Convert.ToDouble(i), Convert.ToDouble(j));
// block the other thread while reading from the raster
m_autoEvent.WaitOne();
//read the pixels from the input raster-band into the pixel-block
rawPixels.Read(tlc, (PixelBlock)pixelBlock);
m_autoEvent.Set();
//set the upper-left corner coordinate in order to write into the subset raster
tlc.SetCoords(Convert.ToDouble(p), Convert.ToDouble(q));
// block the other thread while writing to the output raster
m_autoEvent.WaitOne();
//write the pixels to the output rasterband
newRasterPixels.Write(tlc, (PixelBlock)pixelBlock);
m_autoEvent.Set();
//increment the horizontal coordinate of the output rasterband
p += pixBlockSizeX;
//increment the progressbars
if (ti.BandID < 3)
{
m_progressDlg.IncrementProgressBar(ti.BandID + 1);
}
}
//reset the horizontal coordinate of the output rasterband
p = 0;
//increment the vertical coordinate of the output rasterband
q += pixBlockSizeY;
}
//// wait untill all copy thraeds are done copying
////calculate statistics
////need to lock all other threads in order to write to the rasterband
//m_autoEvent.WaitOne();
////Monitor.Enter(m_lockObject);
//m_progressDlg.SetStatusBarMessage("compute statistics for band #" + (ti.BandID + 1).ToString());
//newRasterBand.ComputeStatsAndHist();
////set the message on the progress dialog statusbar
//m_progressDlg.SetStatusBarMessage("done subsetting band #" + (ti.BandID + 1).ToString());
//////signal the next available thread to write to the subset dataset
////Monitor.Exit(m_lockObject);
//m_autoEvent.Set();
//signal the main thread the that thread has finished its task
ti.DoneEvent.Set();
}
catch (Exception ex)
{
//m_autoEvent.Set();
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}











In molti casi, vuoi sincronizzare l'esecuzione dei concorrenti thread che stanno lavorando. Normalmente, attendi che uno o più thread finiscano il loro task, segni in attesa un thread per proseguire poi il task quando certe condizioni vengono rispettate, verifichi se un dato thread sta ancora lavorando, cambi la priorità del thread o dai delle altre indicazioni.
In .NET ci sono diversi modi di gestire l'esecuzione di thread che stanno lavorando. Le principali classi disponibili per aiutare la gestione thread sono le seguenti:
-System.Threading.Thread: usata per creare e controllare i thread, cambiare la priorità e prendere lo status;
-System.Threading.WaitHandle: definisce un meccanismo per indicare la presa o il rilascio esclusivo a risorse condivise, consentendo di restringere l'accesso ad un blocco di codice. Se chiami il metodo WaitHandle.WaitAll() questo deve essere fatto da un multithreaded apartment (MTA) thread. Per lanciare multipli task sincronizzati, prima devi lanciare un worker thread che, a turno, eseguirà i multipli threads;
-System.Threading.Monitor: simile al System.Threading.WaitHandle provvede ad un meccanismo che sincronizza l'accesso agli oggetti;
-System.Threading.AutoResetEvent e System.Threading.ManualResetEvent: usato per notificare ai threads in attesa che un evento è occorso, consentento ai threads di comunicare l'un con l'altro tramite segnalazione.







Il seguente esempio estende quello coperto precedentemente. Esso usa le classi ManualResetEvent e WaitHandle per attendere che i multipli threads finiscano i loro tasks. In aggiunta dimostra, usando la classe AutoResetEvent, come bloccare i thread in esecuzione dall'accesso ad un blocco di codice e segnalare al prossimo disponibile thread quando il corrente thread ha completato il suo task.









public class TaskInfo
{
//defauld constructor
public 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)
{
try
{
//use rubber-band in order to getthe subset area from the user
IRubberBand2 rubberBand = new RubberEnvelopeClass();
IEnvelope envelope = (IEnvelope)rubberBand.TrackNew(m_pHookHelper.ActiveView.ScreenDisplay, null);
if (null == envelope  envelope.IsEmpty)
return;
// verify that there are layers in the map
if (m_pHookHelper.FocusMap.LayerCount == 0)
return;
// get the top raster layer
m_rasterLayer = null;
IEnumLayer layers = m_pHookHelper.FocusMap.get_Layers(null, false);
layers.Reset();
ILayer layer = layers.Next();
while (layer != null)
{
if (layer is IRasterLayer)
{
m_rasterLayer = (IRasterLayer)layer;
break;
}
layer = layers.Next();
}
if (m_rasterLayer == null)
{
MessageBox.Show("There is no raster layer in the map");
return;
}
// assert if there is no overlap between the user envelope and the top raster
envelope.Intersect(m_rasterLayer.AreaOfInterest);
if (envelope.IsEmpty)
{
MessageBox.Show("The delineated envelope does not intersect the topmost raster in the map");
return;
}
//query number of bands in the input raster
m_intBandCount = m_rasterLayer.BandCount;
IDataset dataset = (IDataset)m_rasterLayer;        
//get the workspace connection props since we'll need to connect it from each of the worker threads
IPropertySet connectionProps = dataset.Workspace.ConnectionProperties;
//get the workspace type
m_workSpaceType = dataset.Workspace.Type;
//serialize the connection properties into a string in order to avoid cross apartment calls
IXMLSerializer xmlSerializer = new XMLSerializerClass();
m_strInputRasterWorkspaceConnectionProps = xmlSerializer.SaveToString(connectionProps, null, null);       
//m_inputRasterName = dataset.Name;
m_inputRasterName = m_rasterLayer.FilePath;
//open a folder-browser in order to get the output location of the subset raster
FolderBrowserDialog folderDlg = new FolderBrowserDialog();
folderDlg.Description = "Select output folder for the subset raster";
folderDlg.ShowNewFolderButton = true;
if (DialogResult.OK != folderDlg.ShowDialog())
return;
//get the output folder that the user had chosen
m_outputRasterPath = folderDlg.SelectedPath;
//get the input raster layer name (will be used in order to create the output raster)
string inputRasterLayerName = m_rasterLayer.Name;
if (inputRasterLayerName.IndexOf(".") != -1)
inputRasterLayerName = inputRasterLayerName.Substring(0, inputRasterLayerName.IndexOf("."));
//set the output format
m_outputFormat = FormatType.IMG;
//create the output raster dataset
CreateRasterDataset(m_rasterLayer, m_outputRasterPath, inputRasterLayerName + "_Subset.img", envelope);
//show the output progress form
m_progressDlg = new frmProgress();
//force the control creation in order to make sure that the form has a handle
m_progressDlg.CreateControl();
//set the statusbar message of the progress dialog
m_progressDlg.SetStatusBarMessage("Subsetting raster " + m_rasterLayer.Name);
//show the progress dialog
m_progressDlg.Show();
//run the subset thread. The subset process uses WaitHandle in order to
//wait on the subsetting threads to finish their task. In order to use WaitHandle.WaitAll()
//the call must be made from a MTA. The current command is STA and therefore a thread must be created 
//in order to allow this(.NET threads gets created as MTA by default).
Thread t = new Thread(new ThreadStart(SubsetProc));
t.Start();
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}



















/// <summary>
/// main subset method
/// </summary>
/// <remarks>Please note that this sample does not copy colormap
/// in case where it is present. The outcome will be black and white</remarks>
private void SubsetProc()
{
try
{
m_lockObject = new object();
//set the message on the progress dialog statusbar  
m_progressDlg.SetStatusBarMessage("starting subset process");
//create ManualResetEvent in order to notify the main threads that all threads 
//are done with their task
ManualResetEvent[] doneEvents = new ManualResetEvent[m_intBandCount];
//create the subset threads
Thread[] threadTask = new Thread[m_intBandCount];
//create a thread for each of the bands of the input raster
//each task will subset a different raster-band. The information required for the 
//subsetting the raster-band will be passed to the task by the user-defined 
//class TaskInfo
for (int i = 0; i < m_intBandCount; i++)
{
//create the ManualResetEvent flad for the task in order to signal the waiting thread
//that the task had been completed
doneEvents[i] = new ManualResetEvent(false);
//create the Task-Information for the thread and pass in the relevant information
//Pleas note that all the information is passed as simple types
TaskInfo ti = new TaskInfo(i, doneEvents[i]);
ti.WorkspaceType = m_workSpaceType;
ti.Rows = m_intRows;
ti.Columns = m_intCols;
ti.I0 = m_i0;
ti.J0 = m_j0;
ti.InputRasterName = m_inputRasterName;
ti.OutputRasterPath = m_outputRasterPath;
ti.OutputRasterName = m_outputRasterName;
ti.InputRasterWSConnectionProps = m_strInputRasterWorkspaceConnectionProps;
// assign the subsetting thread for the rasterband.
threadTask[i] = new Thread(new ParameterizedThreadStart(SubsetRasterBand));
// Note the STA apartment which 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);
}
//Sets the state of the event to signaled, thus allowing one or more of the waiting threads to proceed
m_autoEvent.Set();
// Wait for all threads to complete their task...
WaitHandle.WaitAll(doneEvents);
// verify that all tasks are done and that the memory is cleaned
for (int i = 0; i < m_intBandCount; i++)
{
threadTask[i].Join();
}
threadTask = null;
// spin a new thread in order to claculate statistics and pyramid layers
Thread additionalDataTask = new Thread(new ParameterizedThreadStart(CalculateStatsAndPyramids));
additionalDataTask.SetApartmentState(ApartmentState.STA);
additionalDataTask.Name = "calc_aux_data";
additionalDataTask.Start(System.IO.Path.Combine(m_outputRasterPath, m_outputRasterName));       
}
catch (Exception ex)
{
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}


















/// <summary>
/// subset task method
/// </summary>
/// <param name="state"></param>
private void SubsetRasterBand(object state)
{
try
{
System.Diagnostics.Trace.WriteLine(Thread.CurrentThread.GetApartmentState());
// The state object must be cast to the correct type, because the
// signature of the WaitOrTimerCallback delegate specifies type
// Object.
TaskInfo ti = (TaskInfo)state;
//set the message on the progress dialog statusbar
m_progressDlg.SetStatusBarMessage("Copy RasterBand #" + (ti.BandID + 1).ToString());
IRasterDataset rasterataset = OpenRasterDataset(ti);
//cast the input raster bandCollection
IRasterBandCollection rasterBaneCollection = (IRasterBandCollection)rasterataset;
//get the raster band from which to copy the data
IRasterBand rasterBand = rasterBaneCollection.Item(ti.BandID);
IRawPixels rawPixels = (IRawPixels)rasterBand;
//open the newly created raster dataset
IRasterDataset newRasterDataset = OpenOutputRasterDataset(ti.OutputRasterPath, ti.OutputRasterName);
//get the raster-band into which data will be copied
IRasterBandCollection newRasterBandCollection = (IRasterBandCollection)newRasterDataset;
IRasterBand newRasterBand = newRasterBandCollection.Item(ti.BandID);
IRawPixels newRasterPixels = (IRawPixels)newRasterBand;
//set the size of the pixelBlock
int pixBlockSizeX = Math.Min(128, m_intCols);
int pixBlockSizeY = Math.Min(128, m_intRows);
//calculate the progressbar MaxValue
int hBlocks = Convert.ToInt32(m_intCols / pixBlockSizeX + 0.5);
int vBlocks = Convert.ToInt32(m_intRows / pixBlockSizeY + 0.5);
//set the maximum value of the progress controls of the progress dialog
if (ti.BandID == 0)
m_progressDlg.SetProgressbarMaximum(hBlocks * vBlocks);
//set the upper left pixel from which the subset starts
IPnt pnt = new PntClass();
pnt.SetCoords(Convert.ToDouble(pixBlockSizeX), Convert.ToDouble(pixBlockSizeY));
//instantiate the Top-Left-Corner to be used with the pixel-block for the copying process
IPnt tlc = new PntClass();
Monitor.Enter(m_lockObject);
//create the pixelBlock for the copying process
IPixelBlock pixelBlock = rawPixels.CreatePixelBlock(pnt);
Monitor.Exit(m_lockObject);
int p = 0;
int q = 0;
//read the raster band info from the input raster-band and write it to the new raster-band
for (int j = ti.J0; j < (ti.J0 + m_intRows); j += pixBlockSizeY)
{
for (int i = ti.I0; i < (ti.I0 + m_intCols); i += pixBlockSizeX)
{
//set the upper-left corner coordinate
tlc.SetCoords(Convert.ToDouble(i), Convert.ToDouble(j));
// block the other thread while reading from the raster
m_autoEvent.WaitOne();
//read the pixels from the input raster-band into the pixel-block
rawPixels.Read(tlc, (PixelBlock)pixelBlock);
m_autoEvent.Set();
//set the upper-left corner coordinate in order to write into the subset raster
tlc.SetCoords(Convert.ToDouble(p), Convert.ToDouble(q));
// block the other thread while writing to the output raster
m_autoEvent.WaitOne();
//write the pixels to the output rasterband
newRasterPixels.Write(tlc, (PixelBlock)pixelBlock);
m_autoEvent.Set();
//increment the horizontal coordinate of the output rasterband
p += pixBlockSizeX;
//increment the progressbars
if (ti.BandID < 3)
{
m_progressDlg.IncrementProgressBar(ti.BandID + 1);
}
}
//reset the horizontal coordinate of the output rasterband
p = 0;
//increment the vertical coordinate of the output rasterband
q += pixBlockSizeY;
}
//// wait untill all copy thraeds are done copying
////calculate statistics
////need to lock all other threads in order to write to the rasterband
//m_autoEvent.WaitOne();
////Monitor.Enter(m_lockObject);
//m_progressDlg.SetStatusBarMessage("compute statistics for band #" + (ti.BandID + 1).ToString());
//newRasterBand.ComputeStatsAndHist();
////set the message on the progress dialog statusbar
//m_progressDlg.SetStatusBarMessage("done subsetting band #" + (ti.BandID + 1).ToString());
//////signal the next available thread to write to the subset dataset
////Monitor.Exit(m_lockObject);
//m_autoEvent.Set();
//signal the main thread the that thread has finished its task
ti.DoneEvent.Set();
}
catch (Exception ex)
{
//m_autoEvent.Set();
System.Diagnostics.Trace.WriteLine(ex.Message);
}
}











Talvolta la tua applicazione utilizza strutture dati come oggetti gestiti tipo DataTable o HashTable. Questi oggetti gestiti .NET ti consentono di condividerli attraverso multipli threads come un thread di lettura dati e un thread di rendering. Comunque, consulta il sito MSDN per verificare se un oggetto è thread safe. In molti casi, un oggetto è thread safe per la lettura ma non per la scrittura. Alcune collection implementano un metodo Syncronized, che provvede a sincronizzare la collection. Nei casi dove il tuo oggetto è accessibile da più di un thread, devi acquisire un esclusivo lock in accordo con la sezione Thread safety nell'MSDN, riguardo a questo particolare oggetto. L'acquisizione come esclusivo lock può essere fatta utilizzando uno dei metodi di sincronizzazione descritti nella precedente sezione od utilizzando un lock statement, che marca un blocco come sezione critica per ottenere un esclusivo lock per un dato oggetto. Esso assicura che, qualora un altro thread cerchi di accedere all'oggetto, questo sarà bloccato fino a che l'oggetto verrà rilasciato (uscita dal lock).
La seguente figura mostra come un DataTable è condiviso da threads multipli. Prima però, controlla la DataTable class su MSDN per verificare se è thread safe.




In questa pagina, controlla la sezione Thread Safety, dove dice: "Questo tipo è safe per operazioni di lettura multithreaded. Devi sincronizzare per operazioni di scrittura".
Questo significa che per operazioni di lettura sul DataTable non ci sono problemi, ma devi prevenire che altri thread possano accedere alla tabella quando scrivono su di esso. Il seguente esempio mostra come bloccare questi altri threads:








r = m_locations.NewRow();
r[1] = zip;
r[2] = cityName;
lock(m_locations)
{
m_locations.Rows.Add(r);
}


In molti casi dove stai utilizzando un background thread per eseguire lunghe operazioni, vuoi riportare all'utente uno stato di avanzamento, errori o altre informazioni relative al task che stai facendo nel thread. Questo può essere fatto aggiornando un controllo sulla UI dell'applicazione. Comunque, in Windows, i controlli forms sono legati ad uno specifico thread (generalmente al thread main) e non sono thread safe. Come risultato, devi delegare, e poi fare un marshal, delle chiamate al controllo UI al thread al quale il controllo appartiene. La delega è fatta attraverso la chiamata al metodo Control.Invoke, che esegue il delegato sul thread che appartiene all'handle Window sottostante il controllo. Per verificare se un chiamante deve chiamare un metodo invoke, puoi utilizzare la proprietà Control.InvokeRequired. Devi assicurarti che l'handle del controllo sia creato prima che venga chiamato Control.Invoke o Control.InvokeRequired.
Il seguente esempio dimostra come visualizzare uno stato di avanzamento relativo ad un background task in una user form.
1) Nella user form, dichiara un delegato attraverso il quale passi le informazioni al controllo








public class WeatherItemSelectionDlg : System.Windows.Forms.Form
{
private System.Windows.Forms.GroupBox grpWeatherItems;
private System.Windows.Forms.ListBox lstWeatherItemNames;
private System.Windows.Forms.Button btnRefreshList;
private System.Windows.Forms.Label lblSelect;
private System.Windows.Forms.TextBox txtSelect;
private System.Windows.Forms.CheckBox chkNewSelection;
private System.Windows.Forms.Button btnSelect;
private System.Windows.Forms.Button btnDismiss;
private System.Windows.Forms.ProgressBar progressBar1;
private System.Windows.Forms.ContextMenu contextMenu1;
private System.Windows.Forms.MenuItem menuZoomTo;
//Class members
private IActiveView                                m_activeView                    = null;
private RSSWeatherLayerClass        m_weatherLayer                = null;
private string[]                                    m_cityNames                        = null;
private DataTable                                    m_weatherItemsTable        = null;
// This delegate enables asynchronous calls for setting
// the text property on a TextBox control.
private delegate void IncrementProgressBarCallback();
private delegate void AddListItmCallback(string item);
private delegate void ShowProgressBarCallBack();
private delegate void HideProgressBarCallBack();




2) Nella user form, imposta il metodo per aggiornare la UI del controllo. Notare la chiamata ad Invoke. Il metodo deve avere la stessa firma come la precedente dichiarazione del delegato:








/// <summary>
/// Make Thread-Safe Calls to Windows Forms Controls
/// </summary>
/// <param name="item"></param>
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);
}
}



3) nel background thread, implementa il metodo che utilizza il delegato e passa il messaggio che sarà visualizzato sulla user form







/// <summary>
/// Populate the listbox according to a selection criteria
/// </summary>
private void PopulateSubListProc()
{
//get the selection criteria
string exp = txtSelect.Text;
//in case that the user did not specify a criteria, pupolate the entire citiname list
if(exp == "")
{
PopulateWeatherItemsTableProc();
return;
}
//set query
exp = "CITYNAME LIKE '" + exp + "%'";
//do the criteria selection against the table
DataRow[] rows = m_weatherItemsTable.Select(exp);
//iterate through the selectionset
foreach(DataRow r in rows)
{
//add the cityName to the listbox
AddListItemString(Convert.ToString(r[0]));
}
}


4) Scrivi la chiamata che lancia il background thread stesso, passando il metodo scritto nel punto 3








//spawn a thread to populate the list with items that match the selection criteria
Thread t = new Thread(new ThreadStart(PopulateSubListProc));
t.Start();



In alcune applicazioni multithreads, potresti aver bisogno di fare chiamate agli ArcObjects da differenti threads in esecuzione. Per esempio potresti avere un background thread che prende informazioni da un web service, il quale poi aggiunge un nuovo oggetto sulla mappa, cambia l'estensione della mappa, o esegue uno strumento di geoprocessiong (GP) per fare delle analisi.
Un caso molto comune è chiamare gli ArcObjects da un metodo di un evento Timer.
Comunque, questo può essere evitato trattando i componenti ArcObjects come se fossero un controllo UI e utilizzando Invoke a delegare la chiamata al thread main dove i componenti ArcObjects sono creati. Così facendo, non vengono fatte chiamate cross-apartment.
L'interfaccia ISynchronizeInvoke include i metodi Invoke, BeginInvoke, e EndInvoke. Implementare questi metodi può essere un compito arduo. Invece, dovresti ereditare la tua classe direttamente da System.Windows.Forms.Control od avere una classe di helper che eredita dal controllo. Entrambe le opzioni provvedono ad una semplice ed efficiente soluzione per invocare i metodi.
Il seguente codice impiega una user-defined classe InvokeHelper per invocare un event handler timer per ricentrare il riquadro visible della mappa ed impostare la rotazione della mappa. Nota che alcune delle logiche applicative devono essere fatte nella classe InvokeHelper in aggiunta alla struttura user-defined che è passata dal metodo delegato.








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


Per utilizzare GP in una applicazione asincrona/multithreaded, usa il pattern di esecuzione asincrona esposto in ArcGIS Server 9.2 utilizzando GP Services. Questo pattern abilita il desktop a lavorare con GP in modalità di esecuzione asincrona.




Qui potete trovare gli esempio completi:
RSS weather layer
Multithreaded raster subset
RSS weather 3D layer