2010-02-28 136 views
6

我试图让我的C#应用​​程序多线程,因为有时,我得到一个异常,说我以不安全的方式调用线程。我从来没有在一个程序中做过任何多线程,所以如果我对这个问题听起来有些无知,那么请耐心等待。Windows窗体应用程序中的多线程调用?

我的程序概述是我想做一个性能监视应用程序。这需要使用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; 
} 

回答

7

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

基本上,您需要检查是否需要调用,然后在需要时执行调用。阅读本文后,你应该能够重构你的用户界面,更新代码到这个样子块:

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线程。文档还建议来解决这个问题的另一种方法:如果使用计时器与用户 界面元件,诸如一个形式或 控制

,分配包含定时器到的形式或控制 SynchronizingObject属性,以便 事件被封送到用户接口线程 。

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

+0

好吧,这和后台工作意见似乎非常有用的;但据我所知,该进程本身正在UI线程上运行,但我必须建立一个单独的线程来收集和更新该进程中的数据?一般来说,我将如何能够确定在哪里制作单独的线程? – Waffles 2010-03-03 17:14:37

+0

当定时器“关闭”时,计时器将在第一个可用的ThreadPool线程上执行请求的ElapsedEventHandler委托。因此,无论您要求定时器做什么,都会发生在单独的线程上,而不是在UI线程上。添加一个后台工作人员只会引入另一个线程。 – 2010-03-04 02:09:55

0

你的问题,我认为,是这条线:

crntreadingslbl.Text = cpuReading.ToString(); 

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

这一切都说,为什么不使用perfmon?它是为了目的而构建的。

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 
}