Windows窗体应用程序中的多线程调用?
我试图让我的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;
}
查看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属性,以便 事件被封送到用户接口线程 。
您可能会发现此路线更容易,但我没有尝试过。
你的问题,我认为,是这条线:
crntreadingslbl.Text = cpuReading.ToString();
是运行UI线程之外。您无法更新UI线程之外的UI元素。您需要调用Window上的Invoke以在UI线程上调用新方法。
这一切都说,为什么不使用perfmon?它是为了目的而构建的。
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
}
好吧,这和后台工作意见似乎非常有用的;但据我所知,该进程本身正在UI线程上运行,但我必须建立一个单独的线程来收集和更新该进程中的数据?一般来说,我将如何能够确定在哪里制作单独的线程? – Waffles 2010-03-03 17:14:37
当定时器“关闭”时,计时器将在第一个可用的ThreadPool线程上执行请求的ElapsedEventHandler委托。因此,无论您要求定时器做什么,都会发生在单独的线程上,而不是在UI线程上。添加一个后台工作人员只会引入另一个线程。 – 2010-03-04 02:09:55