Потоки и ArcGIS

Я только что наткнулся на объект Backgroundworker, и это похоже на инструмент, который я ищу, чтобы заставить мой графический интерфейс реагировать при выполнении вычислений. Я пишу плагины ввода-вывода для 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();
}

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


person Chau    schedule 18.03.2009    source источник
comment
Какое расширение IO вы пишете для ArcGIS? Используете ли вы геообработку, расширение каталога, расширение arcmap,...?   -  person Jordan Parmer    schedule 18.03.2009
comment
Он предназначен для использования в ArcMap в качестве кнопки и либо создает персональную базу геоданных, либо считывает объекты из базы геоданных. Это отвечает на ваш вопрос? Мое знакомство с ArcGIS — это медленно-крутой процесс обучения :-)   -  person Chau    schedule 18.03.2009


Ответы (3)


Это правильно, что вы должны использовать потоки 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, вы не должны совместно использовать объекты между двумя потоками. Из вашего кода кажется, что вы получаете доступ к графическому интерфейсу из фонового рабочего: lblStatus.Text = "Done...";, что можно сделать, например. делегат для RunWorkerComplete.

person Zoidberg    schedule 05.05.2009

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

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

person Macros    schedule 18.03.2009
comment
За исключением использования BeginInvoke и EndInvoke, я понимаю Nutshell и использую события для обновления графического интерфейса. Но я не понимаю снижения производительности ArcGIS по сравнению с многопоточностью. Возможно, потому что я недостаточно хорошо понимаю потоки. - person Chau; 18.03.2009
comment
Я не уверен, почему это должно так сильно снижать производительность - у вас есть примеры кода (с использованием backgroundworker и без него)? - person Macros; 18.03.2009

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

Причина первоначальной потери производительности, вероятно, связана с однопоточным апартаментом (STA), который требуется для ArcGIS. Backgroundworker, похоже, является 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);
person Chau    schedule 25.03.2009