Threading e ArcGIS
-
20-08-2019 - |
Pergunta
Eu só tropeçou em toda o objeto BackgroundWorker, e parece que a ferramenta que eu estou procurando fazer a minha GUI responder durante a execução de cálculos. Estou escrevendo plugins IO para ArcGIS.
Eu estou fazendo algum processamento de dados ArcGIS fora, que funciona bem usando o backgroundworker. Mas quando eu estou inserindo os dados no ArcGIS, o backgroundworker parece aumentar o tempo de duração por um fator de 9 ou assim. Colocar o código de processamento fora do método DoWork, aumenta o desempenho por um factor de 9.
Eu li sobre isso várias lugares na rede, mas eu não tenho nenhuma experiência em programação multithread e os termos como STA e MTA não significa nada para mim. texto do link Eu também tentei usar um simples implementação de threading, mas com resultados semelhantes.
Alguém sabe o que eu posso fazer para ser capaz de usar ArcGIS processamento e manutenção de uma GUI responsiva?
EDIT: Eu incluí uma amostra de minha interação com o trabalhador de fundo. Se eu colocar o código localizado no método StartImporting no método cmdStart_Click, ele executa muito mais rápido.
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();
}
Atenciosamente, Casper
Solução 3
Eu continuei tentando encontrar uma solução, e o seguinte é o que eu acabei fazendo. O código é cortar e colar a partir de vários arquivos e apresentados para dar uma idéia do que eu fiz. Ele demonstra como eu posso chamar métodos que estão se comunicando com o ArcGIS utilizando um fio. O código permite-me para atualizar o GUI no segmento principal, anular a operação, e fazer coisas pós-operação. Acabei usando a primeira parte de segmentação a partir do link que eu postei inicialmente.
A razão para a perda inicial de desempenho é provavelmente devido ao single-threaded apartment (STA) que é exigido pelo ArcGIS. O backgroundworker parece ser MTA, portanto, não é apropriado para trabalhar com ArcGIS
Bem, aqui vai, eu espero não ter esquecido nada, e me sinto muito livre para editar a minha solução. Ele vai tanto ajudar-me e, provavelmente, também outras pessoas que desenvolvem material para 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);
Outras dicas
É verdade que você deve usar threads STA quando se trabalha com os objetos COM no ArcGIS. Ainda assim, você pode obter a conveniência do BackgroundWorker, que sempre é uma thread MTA do pool de threads do sistema.
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;
}
}
O segmento STA pode agora usar os métodos do BackgroundWorker, tais como relatórios de progresso, verificando para cancelamento e comunicação de resultados.
protected virtual void StatusUpdateNotify(ProgressState progressState)
{
if (BackgroundWorker.CancellationPending)
{
throw new OperationCanceledException();
}
BackgroundWorker.ReportProgress(progressState.Progress, progressState);
}
Além de usar apenas threads STA quando operando em ArcGIS objetos, você não deve compartilhar objetos entre dois threds. Do seu código parece que você acessar a interface gráfica do trabalho de fundo: lblStatus.Text = "Done...";
, o que poderia ser feito em, por exemplo, o delegado para RunWorkerComplete.
Normalmente, a fim de manter um GUI responsiva você vai querer executar o código que faz o trabalho em um segmento diferente. Isto é feito muito fácil com .net usando o método BeginInvoke: http://msdn.microsoft.com/en-us/library/aa334867(VS.71).aspx
Em poucas palavras, incluir todo o código não GUI em uma classe separada (ou classes) e ao invés de chamar cada meto diretamente você cria um delegado e chamar o método BeginInvoke sobre isso. O método, então, ir para fora e fazê-lo de coisa sem mais interação com o GUI. Se você deseja ter que atualizar o GUI (por exemplo, uma barra de progresso), então você pode aumentar eventos da classe e pegá-los a partir da GUI, contudo, você vai precisar para assegurar que os controles são atualizados em uma forma thread-safe. Se quiser que o GUI para actualização quando da conclusão do método, então você pode usar o método EndInvoke para lidar com esse