1

このガイドに従ってWPF スレッド モデルを作成し、現在の CPU 使用率とピーク時の CPU 使用率を監視して表示する小さなアプリケーションを作成しました。しかし、イベント ハンドラーで現在の CPU とピーク CPU を更新しても、ウィンドウ内の数値はまったく変化しません。デバッグ時に、テキスト フィールドは変更されますが、ウィンドウで更新されないことがわかります。

このようなアプリケーションを構築するのは悪い習慣であり、代わりに MVVM アプローチを採用する必要があると聞いたことがあります。実際、実行時例外なしでこれを実行できたことに驚いた人もいます。とにかく、最初のリンクからコード例/ガイドを見つけたいと思います。

あなたの考えを教えてください!

そして、ここに私のxamlがあります:

<Window x:Class="UsagePeak2.MainWindow"
    xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
    Title="CPU Peak" Height="75" Width="260">
<StackPanel Orientation="Horizontal" VerticalAlignment="Center" >
    <Button Content="Start"  
        Click="StartOrStop"
        Name="startStopButton"
        Margin="5,0,5,0"
        />
    <TextBlock Margin="10,5,0,0">Peak:</TextBlock>
    <TextBlock Name="CPUPeak" Margin="4,5,0,0">0</TextBlock>
    <TextBlock Margin="10,5,0,0">Current:</TextBlock>
    <TextBlock Name="CurrentCPUPeak" Margin="4,5,0,0">0</TextBlock>
</StackPanel>

これが私のコードです

public partial class MainWindow : Window
{
    public delegate void NextCPUPeakDelegate();

    double thisCPUPeak = 0;

    private bool continueCalculating = false;

    PerformanceCounter cpuCounter;

    public MainWindow() : base()
    {
        InitializeComponent();
    }

    private void StartOrStop(object sender, EventArgs e)
    {
        if (continueCalculating)
        {
            continueCalculating = false;
            startStopButton.Content = "Resume";
        }
        else
        {
            continueCalculating = true;
            startStopButton.Content = "Stop";
            startStopButton.Dispatcher.BeginInvoke(
                DispatcherPriority.Normal, new NextCPUPeakDelegate(GetNextPeak));
            //GetNextPeak();
        }
    }

    private void GetNextPeak()
    {

        cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");

        double currentValue = cpuCounter.NextValue();

        CurrentCPUPeak.Text = Convert.ToDouble(currentValue).ToString();

        if (currentValue > thisCPUPeak)
        {
            thisCPUPeak = currentValue;
            CPUPeak.Text = thisCPUPeak.ToString();
        }

        if (continueCalculating)
        {
            startStopButton.Dispatcher.BeginInvoke(
                System.Windows.Threading.DispatcherPriority.SystemIdle,
                new NextCPUPeakDelegate(this.GetNextPeak));
        }
    }
}
4

2 に答える 2

4

ここにはいくつかの問題があります。まず、メインスレッドで作業を行っていますが、非常に回り道をしています。コードをレスポンシブにするのは、この行です。

startStopButton.Dispatcher.BeginInvoke(
    System.Windows.Threading.DispatcherPriority.SystemIdle,
    new NextCPUPeakDelegate(this.GetNextPeak));

BeginInvokeメソッドのドキュメントから(強調鉱山):

Dispatcher が関連付けられているスレッドで、指定されたデリゲートを指定された優先度で非同期的に実行します。

これを高速で送信しているにもかかわらず、バックグラウンド スレッドで発生するメッセージ ループへのポスト バックにより、UI スレッドで作業をキューに入れています。これにより、UI が完全にレスポンシブになります。

つまり、次のGetNextPeakようにメソッドを再構築する必要があります。

private Task GetNextPeakAsync(CancellationToken token)
{
    // Start in a new task.
    return Task.Factory.StartNew(() => {
        // Store the counter outside of the loop.
        var cpuCounter = 
            new PerformanceCounter("Processor", "% Processor Time", "_Total");

        // Cycle while there is no cancellation.
        while (!token.IsCancellationRequested)
        {
            // Wait before getting the next value.
            Thread.Sleep(1000);

            // Get the next value.
            double currentValue = cpuCounter.NextValue();

            if (currentValue > thisCPUPeak)
            {
                thisCPUPeak = currentValue;
            }

            // The action to perform.
            Action<double, double> a = (cv, p) => {
                CurrentCPUPeak.Text = cv.ToString();
                CPUPeak.Text = p.ToString();
            };

            startStopButton.Dispatcher.Invoke(a, 
                new object[] { currentValue, thisCPUPeak });
        }
    }, TaskCreationOptions.LongRunning);
}

上記に関する注意事項:

  • ディランの回答によると、クラスのNextValueメソッドへの呼び出しは、値が通過し始める前に一定の時間が経過する必要があります。PerformanceCounter

  • 操作を停止する必要があるかどうかを示すために、 CancellationToken 構造が使用されます。これにより、バックグラウンドで継続的に実行されるループが駆動されます。

  • メソッドは、背景情報を表すTaskクラスを返します。

  • 単一のバックグラウンド スレッドが読み取りと書き込みが行われる唯一の場所であるため、thisCPUPeak値の周りの同期は必要ありません。クラスのメソッドへの呼び出しには、 渡されたとの値のコピーがあります。のスレッド (UI スレッドを含む)で値にアクセスする場合は、値へのアクセスを同期する必要があります (ほとんどの場合、ステートメントを介して)。InvokeDispatchercurrentValuethisCPUPeakthisCPUPeaklock

ここでTask、クラス レベルでも を保持し、CancellationTokenSource(を生成するCancellationToken)への参照を保持する必要があります。

private Task monitorTask = null;
private CancellationTokenSource cancellationTokenSource = null;

そして、StartOrStopメソッドを変更してタスクを呼び出します

private void StartOrStop(object sender, EventArgs e)
{
    // If there is a task, then stop it.
    if (task != null)
    {
        // Dispose of the source when done.
        using (cancellationTokenSource)
        {
            // Cancel.
            cancellationTokenSource.Cancel();
        }

        // Set values to null.
        task = null;
        cancellationTokenSource = null;

        // Update UI.
        startStopButton.Content = "Resume";
    }
    else
    {
        // Update UI.
        startStopButton.Content = "Stop";

        // Create the cancellation token source, and
        // pass the token in when starting the task.
        cancellationTokenSource = new CancellationTokenSource();
        task = GetNextPeakAsync(cancellationTokenSource.Token);
    }
}

フラグの代わりに、Taskがすでに実行されているかどうかを確認することに注意してください。存在しない場合はTask、ループを開始します。それ以外の場合は、を使用して既存のループをキャンセルしますCancellationTokenSource

于 2012-08-08T18:47:35.850 に答える
3

パフォーマンス カウンターを正しく使用していないため、UI の更新が表示されません。初めてプロセッサ時間パフォーマンス カウンタを照会すると、常に 0 になります。この質問を参照してください。

cpuCounter = new PerformanceCounter("Processor", "% Processor Time", "_Total");

double currentValue = cpuCounter.NextValue();

Thread.Sleep(1000);

currentValue = cpuCounter.NextValue();

これと同じくらい簡単なことで問題は解決しますが、上記のコメントのいくつかの発言を考慮して、より堅牢なソリューションを開発することをお勧めします。

于 2012-08-08T18:28:59.303 に答える