2009-03-18 3 views
1

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

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

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

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

EDIT: Я включил образец моего взаимодействия с фоновым работником. Если я помещаю код в метод 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(); 
} 

С наилучшими пожеланиями, Casper

+0

Какое расширение IO вы пишете для ArcGIS? Используете ли вы геообработку, расширение каталога, расширение arcmap, ...? –

+0

Он предназначен для использования в ArcMap в качестве кнопки, и он либо создает персональную базу геоданных, либо читает функции из базы геоданных. Отвечает ли это на ваш вопрос? Мое предприятие в ArcGIS - это процесс медленного обучения :-) – Chau

ответ

1

Я продолжал пытаться найти решение, и вот что я в итоге сделал. Код вырезается и вставляется из разных файлов и представлен, чтобы дать представление о том, что я сделал. Он демонстрирует, как я могу вызывать методы, которые взаимодействуют с 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

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

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

+0

За исключением использования начала BeginInvoke EndInvoke, я понимаю «Короткую картинку», и я использую события для обновления графического интерфейса. Но это сокращение производительности ArcGIS и потоковой передачи, которое я не получаю. Наверное, потому что я не очень хорошо разбираюсь в резьбе. – Chau

+0

Я не уверен, почему это должно так сильно снизить производительность - есть ли у вас примеры кода (с использованием фонового работающего и не использующего его)? – Macros

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