views:

393

answers:

3

I'm trying to make my C# application multi threaded because sometimes, I get an exception that says I have made a call to a thread in an unsafe manner. I've never done any multi-threading before in a program, so bear with me if I sound kinda ignorant on the issue.

The overview of my program is that I want to make a performance monitoring applicaiton. What this entails is using the process and performance counter class in C# to launch and monitor an application's processor time, and sending that number back to the UI. However, in the method that actually calls the performance counter's nextValue method (which is set to perform every second thanks to a timer), I would sometimes get the aforementioned exception that would talk about calling a thread in an unsafe manner.

I've attached some of the code for your perusal. I know this is kind of a time consuming question, so I'd be really grateful if anyone could offer me any help as to where to make a new thread and how to call it in a safe way. I tried looking at what was up on MSDN, but that just kinda confused me.

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  A: 

Check out Jon Skeet's article on multi-threading, particularly the page on multi-threading winforms. It should fix you right up.

Basically you need to check to see if an invoke is required, and then perform the invoke if needed. After reading the article you should be able to refactor your UI-updating code into blocks that look like this:

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();
}

In your code, an invoke will be required because you are using a Timer. According to the documentation for System.Timers.Timer:

The Elapsed event is raised on a ThreadPool thread.

This means that the OnTimedEvent() method that you set as the Timer's delegate will execute on the next available ThreadPool thread, which will definitely not be your UI thread. The documentation also suggests an alternate way to solve this problem:

If you use the Timer with a user interface element, such as a form or control, assign the form or control that contains the Timer to the SynchronizingObject property, so that the event is marshaled to the user interface thread.

You may find this route easier, but I haven't tried it.

magnifico
Okay, this and the background worker comment seem pretty useful; but as I understand it, the process itself is running on the UI thread, but I have to make a separate thread to collect and update data on that process? In general, how will I be able to tell where to make a separate thread?
Waffles
The timer will execute the requested ElapsedEventHandler delegate on the first available ThreadPool thread when the timer "goes off". So whatever you asked the timer to do will happen on a separate thread, not on the UI thread. Adding a background worker will just introduce yet another thread to the equation.
magnifico
A: 

Your problem, I think, is that this line:

crntreadingslbl.Text = cpuReading.ToString();

Is running outside of the UI thread. You cannot update a UI element outside of the UI thread. You need to call Invoke on the Window to call a new method on the UI thread.

All that said, why not use perfmon? It's built for purpose.

pdr
A: 

The BackGroundWorker component may help you. It is available on the toolbox so you can drag to your form.

This component exposes a set of events to execute tasks in a thread different than the UI thread. You don't have to worry about creating a thread.

All the interaction between the code running on background and the UI controls must be done via the event handlers.

For your scenario you can setup a timer to trigger the background worker at a specific interval.

private void OnTimedEvent(object source, ElapsedEventArgs e)
{
    backgroundWorker.RunWorkerAsync();
}

Then you implement the proper event handlers to actually collect data and update the UI

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Collect performance data and update the UI   
}
Daniel Melo