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

C# Windows窗体应用程序中的多线程调用?,c#,winforms,multithreading,process,performancecounter,C#,Winforms,Multithreading,Process,Performancecounter,我试图让我的C#应用程序多线程,因为有时候,我会遇到一个异常,说我以不安全的方式调用了一个线程。我以前从来没有在程序中做过任何多线程,所以如果我听起来对这个问题有点无知,请容忍我 我的程序概述是,我想做一个性能监控应用程序。这需要使用C#中的进程和性能计数器类来启动和监视应用程序的处理器时间,并将该数字发送回UI。但是,在实际调用性能计数器的nextValue方法(由于计时器设置为每秒执行一次)的方法中,我有时会遇到前面提到的异常,该异常会涉及以不安全的方式调用线程 我附上了一些代码供您阅读。我

我试图让我的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;
}

查看Jon Skeet关于多线程的文章,特别是上的页面。它应该会把你修好的

基本上,您需要检查是否需要调用,然后在需要时执行调用。阅读本文后,您应该能够将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();
}
在代码中,将需要调用,因为您使用的是计时器。根据以下文件:

已用事件在线程池线程上引发

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

如果与用户一起使用计时器 接口元素,如窗体或 控件,指定窗体或控件 它包含到 财产,以便 事件被封送给用户 接口线程


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

我想您的问题在于这条路线:

crntreadingslbl.Text = cpuReading.ToString();
正在UI线程外部运行。不能更新UI线程之外的UI元素。您需要在窗口上调用Invoke来调用UI线程上的新方法

尽管如此,为什么不使用perfmon呢?它是为特定目的而构建的。

该组件可能会帮助您。它位于工具箱中,因此您可以拖动到窗体中

此组件公开一组事件,以在不同于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   
}

好的,这和背景工作者的评论似乎很有用;但据我所知,进程本身是在UI线程上运行的,但我必须创建一个单独的线程来收集和更新该进程上的数据?一般来说,我怎样才能知道在哪里创建一个单独的线程?当计时器“关闭”时,计时器将在第一个可用的线程池线程上执行请求的ElapsedEventHandler委托。因此,您要求计时器执行的任何操作都将发生在单独的线程上,而不是UI线程上。添加一个后台工作人员只会给等式引入另一个线程。