繁体   English   中英

在后台运行无限操作而不影响主线程的最佳方法?

[英]Optimal way to run an infinite operation in the background without affecting the main thread?

我正在开发一个可以跟踪某些 GPU 参数的小应用程序。 我目前正在使用 5 个后台工作人员来跟踪 5 个不同的参数,这些操作会一直运行到我的应用程序关闭为止。 我知道这可能不是一个好方法。 在后台监视这些参数而不必为每个参数创建工作人员的好方法是什么?

编辑:恢复到我现在问的原始问题,问题已重新打开。

仅监控温度的测试文件。

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

在评论中获得一些帮助后,我能够解决这个问题。 这是我的最终工作代码。

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
    }
}

我的建议是废弃技术上过时的BackgroundWorker ,而使用异步循环。 可以使用Task.Run方法将传感器值的读取卸载到ThreadPool线程,并且可以通过等待Task.Delay任务来施加每次迭代之间的空闲时间:

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

你用这种方法得到了什么:

  1. ThreadPool线程在读取传感器之间的空闲期间不会被阻塞。 如果您的应用程序更复杂并且大量使用ThreadPool ,则这一事实会产生影响。 但是对于像这样的简单应用程序,除了显示传感器值之外什么都不做,这个好处主要是学术性的。 ThreadPool线程在空闲期间将无事可做,无论如何它都会被空闲。 在任何情况下,尽可能避免不必要地阻塞线程是一个好习惯。

  2. 您会获得传递给 UI 线程的强类型传感器值。 无需从object类型进行转换,也无需将所有值转换为int s。

  3. 通过读取GPUThermalsGPUUsage等属性引发的任何异常都将在 UI 线程上重新引发。 您的应用程序不会只是停止工作,而不给出任何错误发生的迹象。 这就是在StartMonitoringSensors方法的签名中选择async void的原因。 通常应避免使用Async void,但这是一种特殊情况,其中async void优于async Task

  4. 您会每 500 毫秒获得一次传感器值的一致更新,因为读取传感器值所需的时间不会添加到空闲时间段中。 发生这种情况是因为Task.Delay(500)任务是在读取值之前创建的,然后是await ed。

我对所有现有的答案都有疑问,因为它们都未能实现基本的关注点分离。

然而,为了解决这个问题,我们需要重新定义这个问题。 在这种情况下,我们不想“运行操作”,而是想“观察/订阅”GPU 状态。

在这种情况下,我更喜欢观察而不是 sub/pub,因为工作已经完成,你真的想知道是否有人在听你的树倒在森林里。

因此,这里是 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();
    }
}

然后去消费。

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

}

这里的优点是您可以在多个地方使用不同的线程等重用这个组件。

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM