간단한 시간 출력 기능, UI 갱신이 멈추는 경우

안녕하세요 연속으로 질문을 올리게 되었네요 미리 감사드립니다

문제로 넘어가서 wpf로 시간을 출력하는 프로그램을 만들었는데 꽤 자주 시간이 멈추는 모습을 보입니다

혹시 타이머가 멈추는 건가 싶어서 시간이 갱신 될 때마다 list에 넣고 문제 발생 시 중단해서 확인해보면 시간은 제대로 추가가 되어있는데요

주기가 100ms인데 이 정도면 너무 자주 갱신해서 성능에 영향을 주는 정도는 아닐 것 같은데 어떻게 접근하면 될까요

System.Threading.Timer를 만들고 100ms마다 DateTime.Now로 시간을 대입하는 간단한 프로그램입니다

  public abstract class ViewModelBase : ObservableObject
{
...
}

    public class MainViewModel : ViewModelBase
    {

  private DateTime _CurrentTime;
        public DateTime CurrentTime
        {
            get => _CurrentTime;
            set
            {
                SetProperty(ref _CurrentTime, value);
            }
        }

  public MainViewModel()
        {
            List<DateTime> date = new List<DateTime>();
            _uiTimer = new Timer(new TimerCallback((s) =>
            {
                date.Add(DateTime.Now);
                if (date.Count > 100)
                    date.Clear();
                CurrentTime = DateTime.Now;
            }),
            null, TimeSpan.Zero, TimeSpan.FromMilliseconds(100));
        }

...

// *.xaml
        <Label Content="{Binding CurrentTime}" ContentStringFormat="yyyy-MM-dd hh:mm:ss.fff"/>
1개의 좋아요

올려주신 예제대로 테스트를 해 봤을때 별다른 문제는 없었는데요.
차이점이 있다면 별도의 TaskPeriodicTimer 타이머를 사용하였습니다.
테스트 했던 코드 공유 드릴게요.

public class MainViewModel : ObservableObject, IDisposable
{
    private DateTime _currentTime;
    private PeriodicTimer _timer;
    private Task _timerTask;

    /// <inheritdoc />
    public MainViewModel()
    {
        StartTimer();
    }

    public DateTime CurrentTime { get => _currentTime; set => SetProperty(ref _currentTime, value); }

    /// <inheritdoc />
    public void Dispose()
    {
        _timer.Dispose();
        _timerTask.Dispose();
    }

    /// <summary>
    /// 타이머
    /// </summary>
    private void StartTimer()
    {
        _timer = new PeriodicTimer(TimeSpan.FromMilliseconds(100));

        _timerTask = Task.Run(async () =>
        {
            while (await _timer.WaitForNextTickAsync().ConfigureAwait(false))
            {
                CurrentTime = DateTime.Now;
            }
        });
    }
}
<Label Content="{Binding CurrentTime}"  ContentStringFormat="yyyy-MM-dd hh:mm:ss.fff" />
3개의 좋아요

.net 4.8이라 해당 타이머는 쓸 수가 없네요 ㅠ
답변 감사합니다

1개의 좋아요

올려주신 코드 그대로 테스트를 해 보니 중간에 한번씩 짧은 시간 멈추는 모습을 보였는데요.
아마도 Clear 함수 때문 인것으로 보입니다.
만약에 의도하신 Clear 함수가 100개를 유지하기 위함이라면 자료구조를 LinkedList로 바꾸고 100개가 넘을 때 마지막 원소만 삭제하는 방식으로 구성하시면 어떨까 싶네요.

/// <summary>
/// 타이머
/// </summary>
private void StartTimer()
{
    LinkedList<DateTime> date = new LinkedList<DateTime>();
    _timer = new Timer(new TimerCallback((s) =>
        {
            date.AddFirst(DateTime.Now);
            if (date.Count > 100)
            {
                date.RemoveLast();
            }
            CurrentTime = DateTime.Now;
        }),
        null, TimeSpan.Zero, TimeSpan.FromMilliseconds(100));
}
2개의 좋아요

그건 테스트를 위해서 추가한건데 없애도 동일 증상이 발생합니다

다만 특이사항이 있는데 창에 뭔가 액션을 취하면 시간이 다시 흐르네요

예를들어 버튼을 하나 추가하고 시간이 멈췄을 때 버튼에 마우스를 올리면 시간이 흐르고
창을 잡고 조금씩 움직이면 멈추는 현상이 발생 안 합니다

1개의 좋아요

ui thread 우선순위 등 관련 있던 것으로
dispatcher timer 를 사용하고 priority를 send, databind등 우선순위 높은 것으로 설정하니 멈춤 없이 동작합니다
단 각 옵션 별 영향이 어떤지는 더 알아봐야할 것 같네요

3개의 좋아요

제가 오래전에 WPF를 만져서 기억이 가물가물한데…
Winform에서 (object).Invoke 하듯이, WPF에서도 Dispatcher.Invoke를 써야했던 기억이 있습니다.
Timer 이벤트 안에서 그냥 ui 변경과 관련된 무언가를 시도하면 Cross-thread 오류가 날겁니다.
(ObservableObject 상속받았고, Label 바인딩을 하고 있기 때문에 아마 맞을듯…)
물론 그럴 때는 예외도 잘 발생되지 않아서 찾기가 힘들죠… :sweat_smile:

1개의 좋아요

Ctrl + F5 나 바이너리로 실행해보셨나요?

VS 디버그가 아닌 바이너리 실행에서는 전혀 문제가 없네요.
아마도 디버거 개입으로 인해 느려지는 현상이 있는 거 같슴다.

재밌는 건 100ms 보다 작은 값으로 디버그를 돌리면 오히려 덜 멈춘다는 거…=ㅅ=;;;

3개의 좋아요

그럴수도 있군요 참고하겠습니다!

2개의 좋아요

확실히 디버그 없이 실행해보니까 괜찮네요

그나저나 MVVM의 시작을 올려주신 글을 보고 했는데 여기서 뵈니까 신기하네요
코끼리가 눈에 익더라구요 ㅎㅎ
도움이 많이 되어 감사드립니다

2개의 좋아요