Estoy trabajando en una pequeña aplicación que rastrearía ciertos parámetros de la GPU. Actualmente estoy usando 5 trabajadores en segundo plano para 5 parámetros diferentes que se están rastreando, las operaciones se ejecutan hasta que se cierra mi aplicación. Sé que probablemente esta no sea una buena forma de hacerlo. ¿Cuál sería una buena manera de monitorear estos parámetros en segundo plano sin tener que crear un trabajador para cada parámetro?

Editar: Volví a la pregunta original que hice ahora que se reabrió la pregunta.

Archivo de prueba que monitorea solo la temperatura.

using NvAPIWrapper.GPU;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace TestForm
{
    public partial class Form1 : Form
    {
        private PhysicalGPU[] gpus = PhysicalGPU.GetPhysicalGPUs();

        public Form1()
        {
            InitializeComponent();
            GPUTemperature();
        }

        private void GPUTemperature()
        {
            backgroundWorker1.RunWorkerAsync();
        }

        private void backgroundWorker1_DoWork(object sender, DoWorkEventArgs e)
        {
            while (!backgroundWorker1.CancellationPending)
            {
                foreach (var gpu in gpus)
                {
                    foreach (var sensor in gpu.ThermalInformation.ThermalSensors)
                    {
                        backgroundWorker1.ReportProgress(sensor.CurrentTemperature);
                        Thread.Sleep(500);
                    }
                }
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender,
            ProgressChangedEventArgs e)
        {
            temperature.Text = e.ProgressPercentage.ToString();
        }
    }
}
2
isqrt3 11 jul. 2021 a las 05:25

3 respuestas

La mejor respuesta

Mi sugerencia es eliminar el tecnológicamente obsoleto BackgroundWorker y usar en lugar de un bucle asincrónico. La lectura de los valores del sensor se puede descargar en un subproceso ThreadPool utilizando Task.Run método, y el período de inactividad entre cada iteración se puede imponer esperando un Task.Delay tarea:

public Form1()
{
    InitializeComponent();
    StartMonitoringSensors();
}

async void StartMonitoringSensors()
{
    while (true)
    {
        var delayTask = Task.Delay(500);
        var (temperature, usage, gpuClock, memory, fan) = await Task.Run(() =>
        {
            return
            (
                GPUThermals.CurrentTemperature,
                GPUUsage.CurrentUsage,
                GPUClock.CurrentGPUClock,
                GPUMemory.CurrentMemoryClock,
                GPUFan.CurrentFanRPM
            );
        });
        gpuTemperatureValue.Text = $"{temperature} °C";
        gpuUsageValue.Text = $"{usage} %";
        gpuClockValue.Text = $"{gpuClock} MHz";
        gpuMemoryValue.Text = $"{memory} MHz";
        gpuFanRPMValue.Text = $"{fan} RPM";
        await delayTask;
    }
}

Lo que obtienes con este enfoque:

  1. Un hilo ThreadPool no se bloquea durante el período de inactividad entre la lectura de los sensores. Este hecho sería impactante si su aplicación fuera más compleja y estuviera haciendo un uso intensivo de ThreadPool. Pero para una aplicación simple como esta, que no hace nada más que mostrar los valores del sensor, este beneficio es principalmente académico. El subproceso ThreadPool no tendrá nada más que hacer durante el período de inactividad y, de todos modos, estará inactivo. En cualquier caso, es una buena costumbre evitar bloquear hilos innecesariamente, siempre que pueda.

  2. Obtienes valores de sensor fuertemente tipados que se pasan al hilo de la interfaz de usuario. No es necesario convertir desde el tipo object y no es necesario convertir todos los valores a int s.

  3. Cualquier excepción lanzada al leer las propiedades de GPUThermals, GPUUsage, etc., se volverá a lanzar en el hilo de la interfaz de usuario. Su aplicación no dejará de funcionar sin dar ningún indicio de que sucedió algo malo. Esa es la razón para elegir async void en la firma del método StartMonitoringSensors. Async void debería evitarse en general, pero este es un caso excepcional en el que async void es preferible a async Task.

  4. Obtiene actualizaciones consistentes de los valores del sensor cada 500 ms, porque el tiempo necesario para leer los valores del sensor no se agrega al período inactivo. Esto sucede porque la tarea Task.Delay(500) se crea antes de leer los valores y se await ed después.

0
Theodor Zoulias 12 jul. 2021 a las 10:27

Pude resolver el problema después de recibir ayuda en los comentarios. Aquí está mi código de trabajo final.

using NVIDIAGPU.GPUClock;
using NVIDIAGPU.GPUFan;
using NVIDIAGPU.Memory;
using NVIDIAGPU.Thermals;
using NVIDIAGPU.Usage;
using System.ComponentModel;
using System.Threading;
using System.Windows.Forms;

namespace SysMonitor
{
    public partial class Form1 : Form
    {
        private int[] sensorValues = new int[5];

        public Form1()
        {
            InitializeComponent();

            StartWorkers();
        }

        /// <summary>
        /// Store sensor parameters.
        /// </summary>
        public int[] SensorValues { get => sensorValues; set => sensorValues = value; }

        private void StartWorkers()
        {
            thermalsWorker.RunWorkerAsync();
        }

        #region ThermalWorker

        private void thermalsWorker_DoWork(object sender, DoWorkEventArgs e)
        {
            while (!thermalsWorker.CancellationPending)
            {
                // Assign array values.
                SensorValues[0] = GPUThermals.CurrentTemeperature;
                SensorValues[1] = GPUUsage.CurrentUsage;
                SensorValues[2] = (int)GPUClock.CurrentGPUClock;
                SensorValues[3] = (int)GPUMemory.CurrentMemoryClock;
                SensorValues[4] = GPUFan.CurrentFanRPM;

                // Pass the SensorValues array to the userstate parameter.
                thermalsWorker.ReportProgress(0, SensorValues);
                Thread.Sleep(500);
            }
        }

        private void backgroundWorker1_ProgressChanged(object sender,
            ProgressChangedEventArgs e)
        {
            // Cast user state to array of int and assign values.
            int[] result = (int[])e.UserState;
            gpuTemperatureValue.Text = result[0].ToString() + " °C";
            gpuUsageValue.Text = result[1].ToString() + " %";
            gpuClockValue.Text = result[2].ToString() + " MHz";
            gpuMemoryValue.Text = result[3].ToString() + " MHz";
            gpuFanRPMValue.Text = result[4].ToString() + " RPM";

            
        }
        #endregion ThermalWorker
    }
}
1
isqrt3 12 jul. 2021 a las 06:02

Tengo un problema con todas las respuestas existentes, ya que todas fallan en la separación básica de preocupaciones.

Sin embargo, para solucionar este problema, necesitamos replantear la pregunta. En este caso, no queremos "ejecutar una operación", sino que queremos "observar / suscribir" al estado de la GPU.

Prefiero observar a sub / pub en este caso, ya que el trabajo está hecho y realmente quieres saber si hay alguien escuchando tu árbol cayendo en el bosque.

Por lo tanto, aquí está el código para una implementación de RX.Net.

public class GpuMonitor
{
    private IObservable<GpuState> _gpuStateObservable;

    public IObservable<GpuState> GpuStateObservable => _gpuStateObservable;

    public GpuMonitor()
    {
        _gpuStateObservable = Observable.Create<GpuState>(async (observer, cancellationToken) => 
        {
            while(!cancellationToken.IsCancellationRequested)
            {
                 await Task.Delay(1000, cancellationToken);
                 var result = ....;
                 observer.OnNext(result);
            }
        })
            .SubscribeOn(TaskPoolScheduler.Default)
            .Publish()
            .RefCount();
    }
}

Luego para consumir.

public class Form1
{
    private IDisposable _gpuMonitoringSubscription;


    public Form1(GpuMonitor gpuMon)
    {
        InitializeComponent();
        _gpuMonitoringSubscription = gpuMon.GpuStateObservable
                .ObserveOn(SynchronizationContext.Current)
                .Susbscribe(state => {
                     gpuUsageValue.Text = $"{state.Usage} %";
                     //etc
                });
    }

    protected override void Dispose(bool disposing)
    {
        _gpuMonitoringSubscription.Dispose();
        base.Dispose(disposing);
    }

}

La ventaja aquí es que puede reutilizar este componente en varios lugares, con diferentes hilos, etc.

0
Aron 12 jul. 2021 a las 11:01