【问题标题】:Threading and ArcGIS线程和 ArcGIS
【发布时间】:2010-10-14 02:02:23
【问题描述】:

我刚刚偶然发现了 Backgroundworker 对象,它似乎是我正在寻找的工具,可以让我的 GUI 在执行计算时做出响应。我正在为 ArcGIS 编写 IO 插件。

我正在 ArcGIS 之外进行一些数据处理,使用 backgroundworker 可以正常工作。但是当我将数据插入 ArcGIS 时,后台工作人员似乎将持续时间增加了 9 倍左右。将处理代码放在 DoWork 方法之外,性能提高了 9 倍。

我在网上的几个地方都读到过这方面的内容,但我没有多线程编程方面的经验,而且 STA 和 MTA 之类的术语对我来说毫无意义。 link text 我也尝试过使用简单的线程实现,但结果相似。

有谁知道我可以做些什么来使用 ArcGIS 处理和维护响应式 GUI?

编辑:我已经包含了我与后台工作人员交互的示例。如果我将 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();
}

问候,卡斯帕

【问题讨论】:

  • 您正在为 ArcGIS 编写什么样的 IO 扩展?您是否在使用地理处理、目录扩展、arcmap 扩展……?
  • 在 ArcMap 中用作按钮,它可以创建个人地理数据库或从地理数据库中读取要素。这是否回答你的问题?我对 ArcGIS 的冒险是一个缓慢而陡峭的学习过程 :-)

标签: c# multithreading backgroundworker arcgis


【解决方案1】:

通常,为了维护响应式 GUI,您需要执行在不同线程中工作的代码。 .net 使用 BeginInvoke 方法很容易做到这一点:http://msdn.microsoft.com/en-us/library/aa334867(VS.71).aspx

简而言之,将所有非 GUI 代码包含在一个单独的类(或多个类)中,而不是直接调用每个方法,而是创建一个委托并在其上调用 BeginInvoke 方法。然后该方法将启动并执行此操作,而无需与 GUI 进一步交互。如果您希望它更新 GUI(例如进度条),那么您可以从类中引发事件并从 GUI 中捕获它们,但是您需要确保以线程安全的方式更新控件。如果您希望在方法完成时更新 GUI,那么您可以使用 EndInvoke 方法来处理它

【讨论】:

  • 除了使用 BeginInvoke 和 EndInvoke 之外,我理解“Nutshell”并且我正在使用事件来更新 GUI。但这是我没有得到的 ArcGIS 与线程性能的下降。可能是因为我不太了解线程。
  • 我不知道为什么它会如此降低性能 - 你有代码示例(使用 backgroundworker 而不使用它)吗?
【解决方案2】:

我一直在努力寻找解决方案,以下是我最终要做的。代码是从各种文件中剪切和粘贴的,并呈现给我一个关于我所做的事情的想法。它演示了如何调用使用线程与 ArcGIS 通信的方法。该代码允许我在主线程中更新 GUI、中止操作并执行操作后的工作。我最终使用了我最初发布的链接中的第一个线程部分。

最初性能下降的原因可能是由于 ArcGIS 所需的单线程单元 (STA)。 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);

【讨论】:

    【解决方案3】:

    在 ArcGIS 中处理 COM 对象时应该使用 STA 线程是正确的。尽管如此,您仍然可以获得 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);
    }
    

    除了在对 ArcGIS 对象进行操作时仅使用 STA 线程外,您不应在两个线程之间共享对象。从您的代码中,您似乎可以从后台工作人员访问 GUI:lblStatus.Text = "Done...";,这可以在例如RunWorkerComplete 的委托。

    【讨论】:

      猜你喜欢
      • 2010-09-28
      • 2011-01-07
      • 2021-02-25
      • 2010-09-28
      • 1970-01-01
      • 2015-11-13
      • 2016-02-29
      • 2011-01-23
      • 2013-01-29
      相关资源
      最近更新 更多