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

Foi útil?

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

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top