Windows窗体应用程序中的多线程调用?

时间:2010-02-28 22:40:41

标签: c# winforms multithreading process performancecounter

我正在尝试使我的C#应用​​程序具有多线程,因为有时,我得到一个异常,说我以不安全的方式调用了一个线程。我以前从未在程序中做过任何多线程,所以如果我对这个问题听起来有点无知,请耐心等待。

我的程序概述是我想要进行性能监控应用程序。这需要使用C#中的进程和性能计数器类来启动和监视应用程序的处理器时间,并将该数字发送回UI。但是,在实际调用性能计数器的nextValue方法(由于计时器设置为每秒执行一次)的方法中,我有时会得到上述异常,它会以不安全的方式调用线程。

我附上了一些代码供你细读。我知道这是一个耗时的问题,所以如果有人能为我提供任何帮助,我会非常感激,无论是在哪里制作一个新线程以及如何以安全的方式调用它。我试着看看MSDN上的内容,但这有点让我感到困惑。

private void runBtn_Click(object sender, EventArgs e)
{
    // this is called when the user tells the program to launch the desired program and
    // monitor it's CPU usage.

    // sets up the process and performance counter
    m.runAndMonitorApplication();

    // Create a new timer that runs every second, and gets CPU readings.
    crntTimer = new System.Timers.Timer();
    crntTimer.Interval = 1000;
    crntTimer.Elapsed += new ElapsedEventHandler(OnTimedEvent);
    crntTimer.Enabled = true;
}

private void OnTimedEvent(object source, ElapsedEventArgs e)
{
    // get the current processor time reading 
    float cpuReading = m.getCPUValue();

    // update the current cpu label
    crntreadingslbl.Text = cpuReading.ToString(); // 

}
// runs the application 
public void runAndMonitorApplication()
{
    p = new Process();
    p.StartInfo.UseShellExecute = true;
    p.StartInfo.CreateNoWindow = true;
    p.StartInfo.FileName = fileName;
    p.Start();

    pc = new System.Diagnostics.PerformanceCounter("Process",
                "% Processor Time",
                p.ProcessName,
                true);
}

// This returns the current percentage of CPU utilization for the process
public float getCPUValue()
{
    float usage = pc.NextValue();

    return usage;
}

3 个答案:

答案 0 :(得分:7)

查看Jon Skeet关于多线程的文章,特别是multi-threading winforms上的页面。它应该能解决你的问题。

基本上,您需要检查是否需要调用,然后根据需要执行调用。阅读完本文后,您应该能够将UI更新代码重构为如下所示的块:

private void OnTimedEvent(object source, ElapsedEventArgs e)
{
    // get the current processor time reading 
    float cpuReading = m.getCPUValue();

    if (InvokeRequired)
    {
        // We're not in the UI thread, so we need to call BeginInvoke
        BeginInvoke(new Action(() => crntreadingslbl.Text = cpuReading.ToString()));
        return;
    }
    // Must be on the UI thread if we've got this far
    crntreadingslbl.Text = cpuReading.ToString();
}

在您的代码中,由于您使用的是Timer,因此需要进行调用。根据{{​​3}}的文档:

  

在ThreadPool线程上引发了Elapsed事件。

这意味着您设置为Timer的委托的OnTimedEvent()方法将在下一个可用的ThreadPool线程上执行,该线程肯定不是您的UI线程。该文档还提出了另一种解决此问题的方法:

  

如果您与用户一起使用Timer   界面元素,例如表单或   控制,分配表格或控制   包含Timer的定时器   System.Timers.Timer财产,所以   该事件被封送给用户   界面线程。

您可能会觉得这条路线更容易,但我还没有尝试过。

答案 1 :(得分:0)

我认为你的问题在于这一行:

crntreadingslbl.Text = cpuReading.ToString();

在UI线程之外运行。您无法更新UI线程之外的UI元素。您需要在窗口上调用Invoke以在UI线程上调用新方法。

所有这一切,为什么不使用perfmon?它是为了目的而建造的。

答案 2 :(得分:0)

BackGroundWorker组件可能会对您有所帮助。它在工具箱中可用,因此您可以拖动到表单。

此组件公开一组事件,以在与UI线程不同的线程中执行任务。您不必担心创建线程。

在后台运行的代码与UI控件之间的所有交互必须通过事件处理程序完成。

对于您的方案,您可以设置计时器以按特定间隔触发后台工作程序。

private void OnTimedEvent(object source, ElapsedEventArgs e)
{
    backgroundWorker.RunWorkerAsync();
}

然后,您实现适当的事件处理程序以实际收集数据并更新UI

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Collect performance data and update the UI   
}