简体   繁体   English

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

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

I am working on a small app that would track certain GPU parameters.我正在开发一个可以跟踪某些 GPU 参数的小应用程序。 I am currently using 5 background workers for 5 different parameters that are being tracked, the operations run until my app is closed.我目前正在使用 5 个后台工作人员来跟踪 5 个不同的参数,这些操作会一直运行到我的应用程序关闭为止。 I know this is probably not a good way to do it.我知道这可能不是一个好方法。 What would be a good way to monitor these parameters in the background without having to create a worker for each parameter?在后台监视这些参数而不必为每个参数创建工作人员的好方法是什么?

Edit: Reverted back to the original question that I asked now that the question was reopened.编辑:恢复到我现在问的原始问题,问题已重新打开。

Test file that monitors the temperature only.仅监控温度的测试文件。

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

I was able to solve the issue after getting some help in the comments.在评论中获得一些帮助后,我能够解决这个问题。 Here is my final working code.这是我的最终工作代码。

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

My suggestion is to scrap the technologically obsolete BackgroundWorker , and use instead an asynchronous loop.我的建议是废弃技术上过时的BackgroundWorker ,而使用异步循环。 The reading of the sensor values can be offloaded to a ThreadPool thread by using the Task.Run method, and the idle period between each iteration can be imposed by awaiting a Task.Delay task:可以使用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;
    }
}

What you get with this approach:你用这种方法得到了什么:

  1. A ThreadPool thread is not blocked during the idle period between reading the sensors. ThreadPool线程在读取传感器之间的空闲期间不会被阻塞。 This fact would be impactful if your application was more complex, and was making heavy use of the ThreadPool .如果您的应用程序更复杂并且大量使用ThreadPool ,则这一事实会产生影响。 But for a simple application like this, that does nothing else than displaying sensor values, this benefit is mostly academic.但是对于像这样的简单应用程序,除了显示传感器值之外什么都不做,这个好处主要是学术性的。 The ThreadPool thread will have nothing else to do during the idle period, and it will be idled anyway. ThreadPool线程在空闲期间将无事可做,无论如何它都会被空闲。 In any case, it is a good habit to avoid blocking threads needlessly, whenever you can.在任何情况下,尽可能避免不必要地阻塞线程是一个好习惯。

  2. You get strongly typed sensor values passed to the UI thread.您会获得传递给 UI 线程的强类型传感器值。 No need to cast from the object type, and no need to convert all the values to int s.无需从object类型进行转换,也无需将所有值转换为int s。

  3. Any exceptions thrown by reading the GPUThermals , GPUUsage etc properties, will be rethrown on the UI thread.通过读取GPUThermalsGPUUsage等属性引发的任何异常都将在 UI 线程上重新引发。 Your application will not just stop working, without giving any indication that something wrong happened.您的应用程序不会只是停止工作,而不给出任何错误发生的迹象。 That's the reason for choosing async void in the signature of the StartMonitoringSensors method.这就是在StartMonitoringSensors方法的签名中选择async void的原因。 Async void should be avoided in general , but this is an exceptional case where async void is preferable to async Task . 通常应避免使用Async void,但这是一种特殊情况,其中async void优于async Task

  4. You get consistent updates of the sensor values every 500 msec, because the time needed to read the sensor values is not added to the idle period.您会每 500 毫秒获得一次传感器值的一致更新,因为读取传感器值所需的时间不会添加到空闲时间段中。 This happens because the Task.Delay(500) task is created before reading the values, and it is await ed afterwards.发生这种情况是因为Task.Delay(500)任务是在读取值之前创建的,然后是await ed。

I have a problem with all the existing answers as they all fail on basic separation of concerns.我对所有现有的答案都有疑问,因为它们都未能实现基本的关注点分离。

However, to fix this problem, we need to reframe the question.然而,为了解决这个问题,我们需要重新定义这个问题。 In this case, we do not want to "run an operation", but instead want to "observer/subscribe" to the GPU state.在这种情况下,我们不想“运行操作”,而是想“观察/订阅”GPU 状态。

I prefer observe over sub/pub in this case, as work is done, and you really want to know if there is someone listening to your tree falling in the forest.在这种情况下,我更喜欢观察而不是 sub/pub,因为工作已经完成,你真的想知道是否有人在听你的树倒在森林里。

Therefore, here is the code for an RX.Net implementation.因此,这里是 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();
    }
}

Then to consume.然后去消费。

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

}

The advantage here, is that you can reuse this component in multiple places, with different threads, etc.这里的优点是您可以在多个地方使用不同的线程等重用这个组件。

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

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