2013-08-21 3 views
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-секундный обратный отсчет, как я хочу, но я вижу, что я использую много процессора в этом процессе (12% от моего 8-го потока i7 2600k).

Я полагаю, что я могу уменьшить эту нагрузку много, только обновление пользовательского интерфейса каждые 10 миллисекунд, а не каждую миллисекунду, но я понятия не имею, как это сделать, например, кроме использования if(time % 10 == 0) перед выполнением TimeSpan и обновление пользовательского интерфейса, но я Подозреваю, что это будет так же неэффективно благодаря циклу while.

Я изобретаю колесо? Я хотел бы, чтобы мой таймер был максимально точным (по крайней мере, для записей времени в игровом автомате для слотов, возможно, пользовательский интерфейс не обязательно должен обновляться так часто).

EDIT: Я пробовал комментировать фактические манипуляции с строкой и обновление пользовательского интерфейса, как это предлагается в комментариях. Теперь, когда я запускаю свой поток, весь мой пользовательский интерфейс зависает, пока поток не выйдет, и я все равно получаю 12% использования ЦП. Я подозреваю, что в то время как цикл ел много процессорного времени.

Обновление: Я пошел с мультимедийным таймером (here), отправленным Kohanz, а также ответом Даниэля. Я больше не использую другой поток, я просто делаю один из этих объектов таймера и обработчик события тика вычисляет время между нажатием кнопки запуска и событием тика. Я даже могу установить период для моих тиков до 1 мс, поэтому я получаю свой крутой обратный отсчет, и, по-видимому, он использует 0% процессор :) Я вполне доволен этим.

+0

вы можете создать еще один поток, отвечающий за обновление пользовательского интерфейса, который входит в сон на N миллисекунд после обновления. – OnoSendai

+0

Вы видели класс Timer http://msdn.microsoft.com/en-us/library/system.windows.forms.timer.aspx, который имеет .Net? – hatchet

+3

Если вы не возражаете, я спрашиваю: кто способен читать 10 миллисекундных изменений на ui? Разве не было бы достаточно обновления каждые 100 или 200 мс? Тогда вы можете просто использовать обычный таймер. – Alex

ответ

2

Dont, просто DONT спуститесь по этой дороге. Вы полностью думаете об этом не так. Вы в основном заставляете свою нить замораживать без какой-либо выгоды.

В принципе любая игра работает таким образом: у вас есть цикл обновления, и всякий раз, когда это срабатывает, вы делаете необходимые вещи. Так, например, если вы хотите знать, как много времени, вы спросите своего рода «таймер», сколько прошло с тех пор что-то случилось

Вот гораздо лучший способ справиться с этим:

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

Интересно, Я не думал об этом так. Я обязательно сделаю это для фактического времени круга, но я все равно хочу использовать таймер обратного отсчета. Думаю, я объединю это с обновлением пользовательского интерфейса гораздо реже, спасибо! :) – Logan

+2

Но ... вот в чем проблема: ваш «while» -loop гарантирует, что ваш поток будет использовать 100% процессора в созданном вами потоке. Вы говорите «продолжайте пробовать следующие строки кода», пока не пройдет время. Это гарантирует ~ 100% использования этой темы. –

+0

А также, как и сейчас, этот поток обновляет пользовательский интерфейс каждый раз, когда он запускается. Таким образом, вытесняете два потока на 100%. Теперь я не знаю, какие рамки вы используете для этого, но если пользовательский интерфейс будет «перерисовываться» каждый раз, когда вы вызываете этот обратный вызов, он будет потреблять 100% на этом потоке. Но, к счастью, это не так. Вы должны думать об этом способами «что-то происходит», тогда я должен прочитать таймер и посмотреть, сколько времени прошло. Сортируйте как: «Начало круга» и «Конец круга» или «Требуется пользовательский интерфейс для обновления и отображения прошедшего времени». ТОГДА вы читаете время, иначе вы не –

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

Использование секундомера - это действительно способ, однако, я думаю, что этот пример - способ скомпрометировать, как его использовать. –

+0

@ Daniel MesSer: Вы правы, может быть, я немного увлекся. Хотел показать один способ решения отслеживания laptimes и получить ваши обновления пользовательского интерфейса за один раз. – Alex

Смежные вопросы