Threading и ArcGIS - PullRequest
       31

Threading и ArcGIS

1 голос
/ 18 марта 2009

Я только что наткнулся на объект Backgroundworker, и похоже, что инструмент, который я ищу, чтобы заставить мой GUI реагировать при выполнении вычислений. Я пишу IO плагины для ArcGIS.

Я выполняю некоторую обработку данных вне ArcGIS, которая отлично работает с помощью backgroundworker. Но когда я вставляю данные в ArcGIS, фоновый работник, кажется, увеличивает продолжительность в 9 раз. Размещение кода обработки вне метода DoWork увеличивает производительность в 9 раз.

Я читал об этом в нескольких местах в сети, но у меня нет опыта в многопоточном программировании, и такие термины, как STA и MTA для меня ничего не значат. текст ссылки Я также попытался использовать простую реализацию потоков, но с похожими результатами.

Кто-нибудь знает, что я могу сделать, чтобы использовать ArcGIS для обработки и поддержки адаптивного графического интерфейса?

РЕДАКТИРОВАТЬ: Я включил образец моего взаимодействия с фоновым работником. Если поместить код, расположенный в методе StartImporting, в метод cmdStart_Click, он будет выполняться намного быстрее.

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();
}

С уважением, Каспер

Ответы [ 3 ]

3 голосов
/ 05 мая 2009

Правильно использовать потоки STA при работе с объектами COM в ArcGIS. Тем не менее, вы можете воспользоваться удобством BackgroundWorker, который всегда является MTA-потоком из пула потоков системы.

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;
    }
}

Поток STA теперь может использовать методы BackgroundWorker, такие как отчет о ходе выполнения, проверка отмены и отчет о результатах.

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

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

Помимо использования потоков STA при работе с объектами ArcGIS, вам не следует делить объекты между двумя потоками. Из вашего кода кажется, что вы получаете доступ к GUI из фонового рабочего: lblStatus.Text = "Done...";, что можно сделать, например, в. делегат для RunWorkerComplete.

1 голос
/ 25 марта 2009

Я продолжал пытаться найти решение, и вот что я в итоге сделал. Код вырезан и вставлен из различных файлов и представлен, чтобы дать представление о том, что я сделал. Это демонстрирует, как я могу вызывать методы, которые взаимодействуют с ArcGIS, используя поток. Код позволяет мне обновлять графический интерфейс в главном потоке, прерывать операции и выполнять операции после операции. Я закончил тем, что использовал первую часть темы из ссылки, которую я первоначально разместил.

Причиной первоначальной потери производительности, вероятно, является однопоточная квартира (STA), которая требуется ArcGIS. Фоновый работник выглядит как MTA, поэтому не подходит для работы с ArcGIS

Ну что ж, надеюсь, я ничего не забыл и не стесняюсь редактировать свое решение. Это поможет и мне, и, возможно, другим людям, разрабатывающим материалы для 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);
1 голос
/ 18 марта 2009

Как правило, чтобы поддерживать отзывчивый графический интерфейс, вы захотите выполнить свой код, который выполняет работу в другом потоке. Это очень легко сделать с помощью .net с помощью метода BeginInvoke: http://msdn.microsoft.com/en-us/library/aa334867(VS.71).aspx

В двух словах, включите весь код, отличный от GUI, в отдельный класс (или классы), и вместо непосредственного вызова каждого метода создайте делегат и вызовите для него метод BeginInvoke. Затем метод отключится и выполнит свою работу без дальнейшего взаимодействия с GUI. Если вы хотите, чтобы он обновлял графический интерфейс (например, индикатор выполнения), тогда вы можете вызывать события из класса и извлекать их из графического интерфейса, однако вам необходимо убедиться, что элементы управления обновляются потокобезопасным способом. Если вы хотите, чтобы графический интерфейс обновлялся после завершения метода, вы можете использовать метод EndInvoke для обработки этого

...