0

小さなホーム プロジェクトとして、スロット カー レース用の小さなラップ カウンターを書いています。カウントダウンタイマーを実装したいと思います。これは、テストとして次のことを行いました。

private Thread countdownThread;
private delegate void UpdateTimer(string update);
UpdateTimer ut;
public LapCounterForm()
{
   InitializeComponent();
   //...
   ut += updateTimer;
   countdownThread = new Thread(new ThreadStart(startCountdown));
}

private void startCountdown()
{
    Process.GetCurrentProcess().ProcessorAffinity = new IntPtr(1);
    Process.GetCurrentProcess().PriorityClass = ProcessPriorityClass.High;
    Thread.CurrentThread.Priority = ThreadPriority.AboveNormal;
    System.Diagnostics.Stopwatch stopwatch = new Stopwatch();
    long time = 0;
    stopwatch.Start();

    while (stopwatch.ElapsedMilliseconds <= 5000)
    {
        time = 5000 - stopwatch.ElapsedMilliseconds;
        TimeSpan ts = TimeSpan.FromMilliseconds(time);
        ut(ts.Minutes.ToString().PadLeft(2, '0') + ":" + ts.Seconds.ToString().PadLeft(2, '0') + ":" + ts.Milliseconds.ToString().PadLeft(3, '0'));
    }

}

private void updateTimer(string text)
{
    if (this.InvokeRequired)
    {
        this.Invoke(new Action<String>(ut), new object[] { text });
    }
    else
    {
        lblCountdownClock.Text = text;
    }
}

スレッドを開始すると、機能します。希望どおりに 5 秒のカウントダウンが表示されますが、プロセスで大量の CPU を使用していることがわかります (8 スレッド i7 2600k の 12%)。

ミリ秒ごとではなく、10ミリ秒ごとにUIを更新するだけで、この負荷を大幅に削減できると思いますが、UIを作成して更新するif(time % 10 == 0)前に使用する以外に、その方法がわかりませんが、それは同じようになると思いますTimeSpanwhile ループのおかげで効率が悪い。

車輪の再発明ですか?タイマーをできるだけ正確にしたいです (少なくともスロットカーのラップタイムの記録については、おそらく UI をそれほど頻繁に更新する必要はありません)。

編集: コメントで提案されているように、実際の文字列操作と UI の更新をコメントアウトしてみました。スレッドを開始すると、スレッドが終了するまで UI 全体がハングし、それでも 12% の CPU 使用率が得られます。while ループが多くの CPU 時間を消費していると思われます。

更新: Kohanz によって投稿されたマルチメディア タイマー ( here ) と Daniel の回答を使用しました。もう別のスレッドはまったく使用しません。タイマー オブジェクトの 1 つを作成し、開始ボタンをクリックしてから tick イベントまでの時間を計算する tick イベント ハンドラーを用意するだけです。ティックの期間を 1 ミリ秒に設定することもできるので、見栄えの良いカウントダウンが表示されます。明らかに 0% の CPU を使用しています :) これには非常に満足しています。

4

2 に答える 2

2

いけません、ただこの道を下らないでください。あなたはこれを完全に間違った方法で考えています。基本的に、スレッドをフリーズさせて何のメリットもありません。

基本的に、どのゲームもこのように機能します。更新ループがあり、それがトリガーされるたびに、必要なことを実行します。たとえば、どのくらいの時間を知りたい場合は、何かが起こってからどれくらい経過したかをある種の「タイマー」に尋ねます。

これを処理するためのより良い方法を次に示します。

class MyStopwatch {
    private DateTime _startTime;
    private DateTime _stopTime;

    public void start() {
        _running = true;
        _startTime = DateTime.Now;
    }

    public void stop() {
        _stopTime = DateTime.Now;
        _running = false;
    }

    public double getTimePassed() {
        if(_running) {
            return (DateTime.Now - _startTime).TotalMilliseconds;
        } else {
            return (_stopTime - _startTime).TotalMilliseconds;
        }
    }
}
于 2013-08-21T19:27:48.237 に答える
1

事実から少し後ですが、これは必要なものを達成する方法を示しています。

public class LapTimer : IDisposable
{
    private readonly System.Diagnostics.Stopwatch _stopWatch = new System.Diagnostics.Stopwatch();
    private readonly ConcurrentDictionary<string, List<TimeSpan>> _carLapTimes = new ConcurrentDictionary<string, List<TimeSpan>>();
    private readonly Action<TimeSpan> _countdownReportingDelegate;
    private readonly TimeSpan _countdownReportingInterval;
    private System.Threading.Timer _countDownTimer;
    private TimeSpan _countdownTo = TimeSpan.FromSeconds(5);

    public LapTimer(TimeSpan countdownReportingInterval, Action<TimeSpan> countdownReporter)
    {
        _countdownReportingInterval = countdownReportingInterval;
        _countdownReportingDelegate = countdownReporter;
    }

    public void StartRace(TimeSpan countdownTo)
    {
        _carLapTimes.Clear();
        _stopWatch.Restart();
        _countdownTo = countdownTo;
        _countDownTimer = new System.Threading.Timer(this.CountdownTimerCallback, null, _countdownReportingInterval, _countdownReportingInterval);
    }

    public void RaceComplete()
    {
        _stopWatch.Stop();
        _countDownTimer.Dispose();
        _countDownTimer = null;
    }

    public void CarCompletedLap(string carId)
    {
        var elapsed = _stopWatch.Elapsed;
        _carLapTimes.AddOrUpdate(carId, new List<TimeSpan>(new[] { elapsed }), (k, list) => { list.Add(elapsed); return list; });
    }

    public IEnumerable<TimeSpan> GetLapTimesForCar(string carId)
    {
        List<TimeSpan> lapTimes = null;
        if (_carLapTimes.TryGetValue(carId, out lapTimes))
        {
            yield return lapTimes[0];
            for (int i = 1; i < lapTimes.Count; i++)
                yield return lapTimes[i] - lapTimes[i - 1];
        }
        yield break;
    }

    private void CountdownTimerCallback(object state)
    {
        if (_countdownReportingDelegate != null)
            _countdownReportingDelegate(_countdownTo - _stopWatch.Elapsed);
    }

    public void Dispose()
    {
        if (_countDownTimer != null)
        {
            _countDownTimer.Dispose();
            _countDownTimer = null;
        }
    }
}

class Program
{
    public static void Main(params string[] args)
    {
        using (var lapTimer = new LapTimer(TimeSpan.FromMilliseconds(100), remaining => Console.WriteLine(remaining)))
        {
            lapTimer.StartRace(TimeSpan.FromSeconds(5));
            System.Threading.Thread.Sleep(2000);
            lapTimer.RaceComplete();
        }
        Console.ReadLine();
    }
}
于 2013-08-21T20:35:52.917 に答える