Многопоточность вызовов в приложении Windows Forms? - PullRequest
6 голосов
/ 01 марта 2010

Я пытаюсь сделать свое приложение C # многопоточным, потому что иногда я получаю исключение, которое говорит, что я сделал вызов потоку небезопасным способом. Я никогда раньше не занимался многопоточностью в программе, так что терпите меня, если я немного неосведомлен в этом вопросе.

Обзор моей программы заключается в том, что я хочу создать приложение для мониторинга производительности. Это влечет за собой использование класса C и счетчика производительности в C # для запуска и мониторинга времени процессора приложения и отправки этого числа обратно в пользовательский интерфейс. Однако в методе, который фактически вызывает метод 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;
}

Ответы [ 3 ]

7 голосов
/ 01 марта 2010

Ознакомьтесь со статьей Джона Скита о многопоточности, особенно на странице многопоточность 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();
}

В вашем коде потребуется вызов, потому что вы используете таймер. Согласно документации для System.Timers.Timer :

Событие Elapsed возникает в потоке ThreadPool.

Это означает, что метод OnTimedEvent (), который вы задали в качестве делегата Timer, будет выполняться в следующем доступном потоке ThreadPool, который определенно не будет вашим потоком пользовательского интерфейса. В документации также предлагается альтернативный способ решения этой проблемы:

Если вы используете таймер с пользователем элемент интерфейса, такой как форма или контролировать, назначить форму или контроль который содержит таймер к SynchronizingObject свойство, так что событие маршалируется пользователю интерфейсная нить.

Вы можете найти этот маршрут проще, но я не пробовал.

0 голосов
/ 01 марта 2010

Компонент BackGroundWorker может вам помочь. Он доступен на панели инструментов, поэтому вы можете перетащить его на форму.

Этот компонент предоставляет набор событий для выполнения задач в потоке, отличном от потока пользовательского интерфейса. Вам не нужно беспокоиться о создании темы.

Все взаимодействие между кодом, работающим в фоновом режиме, и элементами управления пользовательского интерфейса должно осуществляться через обработчики событий.

Для вашего сценария вы можете настроить таймер для запуска фонового работника через определенный интервал.

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

Затем вы реализуете соответствующие обработчики событий для фактического сбора данных и обновления пользовательского интерфейса

private void backgroundWorker_DoWork(object sender, DoWorkEventArgs e)
{
    // Collect performance data and update the UI   
}
0 голосов
/ 01 марта 2010

Ваша проблема, я думаю, в том, что эта строка:

crntreadingslbl.Text = cpuReading.ToString();

Работает вне потока пользовательского интерфейса. Вы не можете обновить элемент пользовательского интерфейса за пределами потока пользовательского интерфейса. Вам нужно вызвать Invoke для окна, чтобы вызвать новый метод в потоке пользовательского интерфейса.

Все это говорит, почему бы не использовать perfmon? Он построен для цели.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...