6

我正在尝试使我的 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;
}
4

3 回答 3

7

查看 Jon Skeet 关于多线程的文章,尤其是关于多线程 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();
}

在您的代码中,将需要调用,因为您使用的是计时器。根据System.Timers.Timer的文档:

Elapsed 事件在 ThreadPool 线程上引发。

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

如果将 Timer 与用户界面元素(例如窗体或控件)一起使用,请将包含 Timer 的窗体或控件分配给 SynchronizingObject 属性,以便将事件封送至用户界面线程。

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

于 2010-02-28T22:42:42.643 回答
0

我认为你的问题是这一行:

crntreadingslbl.Text = cpuReading.ToString();

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

说了这么多,为什么不使用 perfmon 呢?它是为目的而建造的。

于 2010-02-28T22:48:22.723 回答
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   
}
于 2010-03-01T01:49:28.190 回答