簡體   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