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