Question

Je viens de trébucher sur l'objet Backgroundworker et il semble que ce soit l'outil que je recherche pour que mon interface graphique réponde pendant les calculs. J'écris des plug-ins IO pour ArcGIS.

Je suis en train de traiter des données en dehors d'ArcGIS, ce qui fonctionne bien avec le backgroundworker. Mais lorsque j'insère les données dans ArcGIS, le travail en arrière-plan semble augmenter la durée d'un facteur 9 environ. Le fait de placer le code de traitement en dehors de la méthode DoWork augmente les performances d'un facteur 9.

J'ai lu à ce sujet plusieurs endroits sur le net, mais je n'ai aucune expérience de la programmation multithread et les termes tels que STA et MTA ne signifient rien pour moi. texte du lien J'ai également essayé d'utiliser une simple implémentation de threading, mais avec des résultats similaires.

Quelqu'un sait-il ce que je peux faire pour pouvoir utiliser le traitement ArcGIS et gérer une interface graphique réactive?

EDIT: J'ai inclus un échantillon de mon interaction avec l'agent de travail en arrière-plan. Si je mets le code situé dans la méthode StartImporting dans la méthode cmdStart_Click, il s'exécutera beaucoup plus rapidement.

private void StartImporting(object sender, DoWorkEventArgs e)
{
    DateTime BeginTime = DateTime.Now;
    // Create a new report object.
    SKLoggingObject loggingObject = new SKLoggingObject("log.txt");
    loggingObject.Start("Testing.");

    SKImport skImporter = new SKImport(loggingObject);
    try
    {
        // Read from a text box - no writing.
    skImporter.Open(txtInputFile.Text);
    }
    catch
    {
    }
    SKGeometryCollection convertedCollection = null;

    // Create a converter object.
    GEN_SK2ArcGIS converter = new GEN_SK2ArcGIS(loggingObject);

    // Convert the data.
    convertedCollection = converter.Convert(skImporter.GetGeometry());

    // Create a new exporter.
    ArcGISExport arcgisExporter = new ArcGISExport(loggingObject);

    // Open the file.            
    // Read from a text box - no writing.
    arcgisExporter.Open(txtOutputFile.Text);

    // Insert the geometry collection.
    try
    {
    arcgisExporter.Insert(convertedCollection);
    }
    catch
    {
    }
    TimeSpan totalTime = DateTime.Now - BeginTime;
    lblStatus.Text = "Done...";

}

private void ChangeProgress(object sender, ProgressChangedEventArgs e) 
{
    // If any message was passed, display it.
    if (e.UserState != null && !((string)e.UserState).Equals(""))
    {
    lblStatus.Text = (string)e.UserState;
    }
    // Update the progress bar.
    pgStatus.Value = e.ProgressPercentage;
}

private void ImportDone(object sender, RunWorkerCompletedEventArgs e)
{
    // If the process was cancelled, note this.
    if (e.Cancelled)
    {
    pgStatus.Value = 0;
    lblStatus.Text = "Operation was aborted by user...";
    }
    else
    {
    }

}

private void cmdStart_Click(object sender, EventArgs e)
{
    // Begin importing the sk file to the geometry collection.

    // Initialise worker.
    bgWorker = new BackgroundWorker();
    bgWorker.RunWorkerCompleted += new RunWorkerCompletedEventHandler(ImportDone);
    bgWorker.ProgressChanged += new ProgressChangedEventHandler(ChangeProgress);
    bgWorker.DoWork += new DoWorkEventHandler(StartImporting);
    bgWorker.WorkerReportsProgress = true;
    bgWorker.WorkerSupportsCancellation = true;

    // Start worker.
    bgWorker.RunWorkerAsync();

}

private void cmdCancel_Click(object sender, EventArgs e)
{
    bgWorker.CancelAsync();
}

Cordialement, Casper

Était-ce utile?

La solution 3

J'ai continué à essayer de trouver une solution et voici ce que j'ai fini par faire. Le code est copié et collé à partir de divers fichiers et présenté pour donner une idée de ce que j'ai fait. Cela montre comment appeler des méthodes qui communiquent avec ArcGIS à l'aide d'un thread. Le code me permet de mettre à jour l'interface graphique dans le thread principal, d'abandonner l'opération et d'effectuer des opérations post-opération. J'ai fini par utiliser la première partie de filetage du lien que j'avais posté initialement.

La raison de la perte de performance initiale est probablement due à la solution STA (Single-Threaded Apartment) requise par ArcGIS. Backgroundworker semble être MTA et ne convient donc pas pour travailler avec ArcGIS

Eh bien voilà, j'espère ne rien avoir oublié et je me sens très libre d'éditer ma solution. Cela m'aidera et aidera probablement aussi d'autres personnes à développer des outils pour ArcGIS.

public class Program
{
    private volatile bool AbortOperation;
    Func<bool> AbortOperationDelegate;
    FinishProcessDelegate finishDelegate;
    UpdateGUIDelegate updateGUIDelegate;

    private delegate void UpdateGUIDelegate(int progress, object message);
    private delegate void FinishProcessDelegate();

    private void cmdBegin_Click(...)
    {
        // Create finish delegate, for determining when the thread is done.
        finishDelegate = new FinishProcessDelegate(ProcessFinished);
        // A delegate for updating the GUI.
        updateGUIDelegate = new UpdateGUIDelegate(UpdateGUI);
        // Create a delegate function for abortion.
        AbortOperationDelegate = () => AbortOperation;

        Thread BackgroundThread = new Thread(new ThreadStart(StartProcess));            
        // Force single apartment state. Required by ArcGIS.
        BackgroundThread.SetApartmentState(ApartmentState.STA);
        BackgroundThread.Start();
    }

    private void StartProcess()
    {    
        // Update GUI.
        updateGUIDelegate(0, "Beginning process...");

        // Create object.
        Converter converter = new Converter(AbortOperationDelegate);
        // Parse the GUI update method to the converter, so it can update the GUI from within the converter. 
        converter.Progress += new ProcessEventHandler(UpdateGUI);
        // Begin converting.
        converter.Execute();

        // Tell the main thread, that the process has finished.
        FinishProcessDelegate finishDelegate = new FinishProcessDelegate(ProcessFinished);
        Invoke(finishDelegate);

        // Update GUI.
        updateGUIDelegate(100, "Process has finished.");
    }

    private void cmdAbort_Click(...)
    {
        AbortOperation = true;
    }

    private void ProcessFinished()
    {
        // Post processing.
    }

    private void UpdateGUI(int progress, object message)
    {
        // If the call has been placed at the local thread, call it on the main thread.
        if (this.pgStatus.InvokeRequired)
        {
            UpdateGUIDelegate guidelegate = new UpdateGUIDelegate(UpdateGUI);
            this.Invoke(guidelegate, new object[] { progress, message });
        }
        else
        {
            // The call was made on the main thread, update the GUI.
            pgStatus.Value = progress;
            lblStatus.Text = (string)message;   
        }
    }
}

public class Converter
{
    private Func<bool> AbortOperation { get; set;}

    public Converter(Func<bool> abortOperation)
    {
        AbortOperation = abortOperation;
    }

    public void Execute()
    {
        // Calculations using ArcGIS are done here.
        while(...) // Insert your own criteria here.
        {
            // Update GUI, and replace the '...' with the progress.
            OnProgressChange(new ProgressEventArgs(..., "Still working..."));

            // Check for abortion at anytime here...
            if(AbortOperation)
            {
                return;
            }
        }
    }

    public event ProgressEventHandler Progress;
    private virtual void OnProgressChange(ProgressEventArgs e)
    {
        var p = Progress;
        if (p != null)
        {
            // Invoke the delegate. 
        p(e.Progress, e.Message);
        }
    }    
}

public class ProgressEventArgs : EventArgs
{
    public int Progress { get; set; }
    public string Message { get; set; }
    public ProgressEventArgs(int _progress, string _message)
    {
        Progress = _progress;
        Message = _message;
    }
}

public delegate void ProgressEventHandler(int percentProgress, object userState);

Autres conseils

Il est correct d'utiliser des threads STA lorsque vous utilisez des objets COM dans ArcGIS. Néanmoins, vous pouvez bénéficier de la commodité de BackgroundWorker, qui est toujours un thread MTA du pool de threads du système.

private static void OnBackgroundWorkerDoWork(object sender, DoWorkEventArgs e)
{
    BackgroundWorker worker = (BackgroundWorker)sender;
    ToolToStart tool = e.Argument as ToolToStart;

    if (tool != null)
    {
        tool.BackgroundWorker = worker;

        // The background worker thread is an MTA thread, 
        // and should not operate on ArcObjects/COM types.
        // Instead we create an STA thread to run the tool in.
        // When the the tool finishes the infomation from the STA thread 
        // is transferred to the background worker's event arguments.
        Thread toolThread = new Thread(STAThreadStart);
        toolThread.SetApartmentState(ApartmentState.STA);
        toolThread.Start(tool);

        toolThread.Join();
        e.Cancel = m_ToolCanceled;
        e.Result = m_ToolResult;
    }
}

Le thread STA peut désormais utiliser les méthodes de BackgroundWorker, telles que le rapport d'avancement, la vérification de l'annulation et les résultats du rapport.

protected virtual void StatusUpdateNotify(ProgressState progressState)
{
    if (BackgroundWorker.CancellationPending)
    {
        throw new OperationCanceledException();
    }

    BackgroundWorker.ReportProgress(progressState.Progress, progressState);
}

Outre l'utilisation de threads STA lors de l'utilisation d'objets ArcGIS, vous ne devez pas partager d'objets entre deux threds. À partir de votre code, il semble que vous accédiez à l'interface graphique à partir de l'agent d'arrière-plan: lblStatus.Text = " Done ... " ;; , ce qui pourrait être fait, par exemple. le délégué de RunWorkerComplete.

Généralement, afin de maintenir une interface graphique réactive, vous souhaiterez exécuter votre code exécutant le travail dans un autre thread. Cela est très facile avec .net en utilisant la méthode BeginInvoke: http://msdn.microsoft.com/en-us/library/aa334867(VS.71).aspx

En un mot, incluez tout le code non-graphique dans une classe séparée et plutôt que d’appeler chaque méthode directement, vous créez un délégué et appelez la méthode BeginInvoke. La méthode s’exécutera ensuite sans autre interaction avec l’interface graphique. Si vous souhaitez qu'il mette à jour l'interface graphique (par exemple, une barre de progression), vous pouvez alors déclencher des événements de la classe et les récupérer à partir de l'interface graphique. Toutefois, vous devrez vous assurer que les contrôles sont mis à jour de manière sécurisée pour les threads. Si vous souhaitez que l'interface graphique se mette à jour à l'issue de la méthode, vous pouvez utiliser la méthode EndInvoke pour gérer cette

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top