Dreamine.Threading.Wpf 1.0.1
이 패키지는 Worker Thread를 직접 생성하거나 제어하지 않습니다. `IDreamineThreadManager`의 상태만 관찰합니다.
로딩중...
검색중...
일치하는것 없음
DreamineThreadMonitorViewModel.cs
이 파일의 문서화 페이지로 가기
1using System;
2using System.Collections.Generic;
3using System.Collections.ObjectModel;
4using System.ComponentModel;
5using System.Linq;
6using System.Runtime.CompilerServices;
7using System.Threading;
8using System.Threading.Tasks;
9using System.Windows.Input;
10using System.Windows.Threading;
11using Dreamine.MVVM.ViewModels;
12using Dreamine.Threading.Interfaces;
13using Dreamine.Threading.Models;
15
17
34public sealed class DreamineThreadMonitorViewModel : INotifyPropertyChanged, IDisposable
35{
44 public static readonly TimeSpan DefaultRefreshInterval = TimeSpan.FromMilliseconds(500);
45
54 private readonly IDreamineThreadManager _threadManager;
72 private readonly Timer _refreshTimer;
81 private readonly BatchedDispatcher<IReadOnlyList<DreamineThreadInfo>> _uiBatch;
90 private readonly Dictionary<string, ThreadInfoRow> _rowsByName = new(StringComparer.Ordinal);
91
109 private int _disposed;
110
119 private readonly RelayCommand _startCommand;
128 private readonly AsyncRelayCommand _stopCommand;
137 private readonly RelayCommand _pauseCommand;
146 private readonly RelayCommand _resumeCommand;
155 private readonly RelayCommand _refreshCommand;
164 private readonly RelayCommand _startAllCommand;
173 private readonly AsyncRelayCommand _stopAllCommand;
182 private readonly RelayCommand _pauseAllCommand;
191 private readonly RelayCommand _resumeAllCommand;
192
201 public event PropertyChangedEventHandler? PropertyChanged;
202
211 public ObservableCollection<ThreadInfoRow> Threads { get; } = new();
212
221 public ICommand StartCommand => _startCommand;
222
231 public ICommand StopCommand => _stopCommand;
232
241 public ICommand PauseCommand => _pauseCommand;
242
251 public ICommand ResumeCommand => _resumeCommand;
252
261 public ICommand RefreshCommand => _refreshCommand;
262
272
281 public ICommand StopAllCommand => _stopAllCommand;
282
292
302
312 {
313 get => _selectedThread;
314 set
315 {
316 if (ReferenceEquals(_selectedThread, value))
317 {
318 return;
319 }
320
321 _selectedThread = value;
325 }
326 }
327
336 public string SelectedDetailText
337 {
338 get
339 {
340 var row = SelectedThread;
341 if (row is null)
342 {
343 return string.Empty;
344 }
345
346 return
347 $"Name: {row.Name}{Environment.NewLine}" +
348 $"Status: {row.Status}{Environment.NewLine}" +
349 $"Priority: {row.Priority}{Environment.NewLine}" +
350 $"Interval: {row.IntervalMs} ms{Environment.NewLine}" +
351 $"Core: {(row.CoreIndex?.ToString() ?? "None")}{Environment.NewLine}" +
352 $"Affinity: {row.UseAffinity}{Environment.NewLine}" +
353 $"Job Count: {row.JobCount}{Environment.NewLine}" +
354 $"Cycle Count: {row.CycleCount}{Environment.NewLine}" +
355 $"Started At: {row.StartedAt}{Environment.NewLine}" +
356 $"Stopped At: {row.StoppedAt}{Environment.NewLine}" +
357 $"Last Error: {row.LastErrorMessage}";
358 }
359 }
360
394 IDreamineThreadManager threadManager,
395 IThreadUiDispatcher dispatcher)
396 : this(threadManager, dispatcher, DefaultRefreshInterval)
397 {
398 }
399
441 IDreamineThreadManager threadManager,
442 IThreadUiDispatcher dispatcher,
443 TimeSpan refreshInterval)
444 {
445 _threadManager = threadManager ?? throw new ArgumentNullException(nameof(threadManager));
446 _dispatcher = dispatcher ?? throw new ArgumentNullException(nameof(dispatcher));
447
448 if (refreshInterval <= TimeSpan.Zero)
449 {
450 refreshInterval = DefaultRefreshInterval;
451 }
452
453 _uiBatch = new BatchedDispatcher<IReadOnlyList<DreamineThreadInfo>>(
454 dispatcher.Dispatcher,
456 DispatcherPriority.Background);
457
459 _stopCommand = new AsyncRelayCommand(StopSelectedThread, HasSelectedThread);
462 _refreshCommand = new RelayCommand(Refresh);
463 _startAllCommand = new RelayCommand(StartAllThreads);
464 _stopAllCommand = new AsyncRelayCommand(StopAllThreads);
465 _pauseAllCommand = new RelayCommand(PauseAllThreads);
466 _resumeAllCommand = new RelayCommand(ResumeAllThreads);
467
468 // Initial sync refresh so the grid is populated before the timer fires.
469 Refresh();
470
471 _refreshTimer = new Timer(
473 null,
474 refreshInterval,
475 refreshInterval);
476 }
477
494 public void Refresh()
495 {
496 if (Volatile.Read(ref _disposed) != 0)
497 {
498 return;
499 }
500
501 var infos = _threadManager.GetThreadInfos();
502 _uiBatch.Enqueue(infos);
503 }
504
529 private void OnTimerTick(object? state)
530 {
531 // Timer callback runs on a thread-pool thread. Just snapshot and enqueue;
532 // the UI-thread merge happens in ApplySnapshotsOnUiThread.
533 if (Volatile.Read(ref _disposed) != 0)
534 {
535 return;
536 }
537
538 try
539 {
540 var infos = _threadManager.GetThreadInfos();
541 _uiBatch.Enqueue(infos);
542 }
543 catch
544 {
545 // Polling failures must not bring down the UI.
546 }
547 }
548
573 private void ApplySnapshotsOnUiThread(IReadOnlyList<IReadOnlyList<DreamineThreadInfo>> batch)
574 {
575 // UI thread. Use only the most recent snapshot in the batch — older
576 // snapshots inside the same batch are stale by definition.
577 if (Volatile.Read(ref _disposed) != 0 || batch.Count == 0)
578 {
579 return;
580 }
581
582 var latest = batch[batch.Count - 1];
583
584 // Track which existing rows are still present.
585 var seen = new HashSet<string>(StringComparer.Ordinal);
586 var anyStructuralChange = false;
587 var selectedRowChanged = false;
588
589 // Apply additions / updates.
590 for (var i = 0; i < latest.Count; i++)
591 {
592 var info = latest[i];
593 seen.Add(info.Name);
594
595 if (_rowsByName.TryGetValue(info.Name, out var existing))
596 {
597 var changed = existing.UpdateFrom(info);
598 if (changed && ReferenceEquals(existing, _selectedThread))
599 {
600 selectedRowChanged = true;
601 }
602 }
603 else
604 {
605 var row = new ThreadInfoRow(info);
606 _rowsByName[info.Name] = row;
607 Threads.Add(row);
608 anyStructuralChange = true;
609 }
610 }
611
612 // Apply removals.
613 for (var i = Threads.Count - 1; i >= 0; i--)
614 {
615 var row = Threads[i];
616 if (!seen.Contains(row.Name))
617 {
618 Threads.RemoveAt(i);
619 _rowsByName.Remove(row.Name);
620
621 if (ReferenceEquals(row, _selectedThread))
622 {
623 SelectedThread = null;
624 }
625
626 anyStructuralChange = true;
627 }
628 }
629
630 if (selectedRowChanged)
631 {
632 // Selected row's underlying fields changed — refresh the detail text.
634 }
635
636 if (anyStructuralChange || selectedRowChanged)
637 {
639 }
640 }
641
658 public void Dispose()
659 {
660 if (Interlocked.Exchange(ref _disposed, 1) != 0)
661 {
662 return;
663 }
664
665 // Wait for an in-flight timer callback when the runtime can signal it.
666 // Dispose can return false when the timer is already disposed or no
667 // callback can be signaled; in that case disposal remains best-effort.
668 // The wait is bounded so UI shutdown cannot hang indefinitely.
669 using var disposed = new ManualResetEvent(false);
670 try
671 {
672 if (_refreshTimer.Dispose(disposed))
673 {
674 disposed.WaitOne(TimeSpan.FromSeconds(1));
675 }
676 }
677 catch
678 {
679 // Suppress on dispose.
680 }
681 }
682
699 private bool HasSelectedThread() => SelectedThread is not null;
700
709 private void StartSelectedThread()
710 {
711 var name = SelectedThread?.Name;
712 if (name is null) return;
713
714 _threadManager.Start(name);
715 Refresh();
716 }
717
734 private async Task StopSelectedThread()
735 {
736 var name = SelectedThread?.Name;
737 if (name is null) return;
738
739 await _threadManager.StopAsync(name).ConfigureAwait(true);
740 Refresh();
741 }
742
751 private void PauseSelectedThread()
752 {
753 var name = SelectedThread?.Name;
754 if (name is null) return;
755
756 _threadManager.Pause(name);
757 Refresh();
758 }
759
768 private void ResumeSelectedThread()
769 {
770 var name = SelectedThread?.Name;
771 if (name is null) return;
772
773 _threadManager.Resume(name);
774 Refresh();
775 }
776
785 private void StartAllThreads()
786 {
787 _threadManager.StartAll();
788 Refresh();
789 }
790
807 private async Task StopAllThreads()
808 {
809 await _threadManager.StopAllAsync().ConfigureAwait(true);
810 Refresh();
811 }
812
821 private void PauseAllThreads()
822 {
823 _threadManager.PauseAll();
824 Refresh();
825 }
826
835 private void ResumeAllThreads()
836 {
837 _threadManager.ResumeAll();
838 Refresh();
839 }
840
849 private void RaiseCommandStates()
850 {
851 _startCommand.RaiseCanExecuteChanged();
852 _stopCommand.RaiseCanExecuteChanged();
853 _pauseCommand.RaiseCanExecuteChanged();
854 _resumeCommand.RaiseCanExecuteChanged();
855 }
856
873 private void OnPropertyChanged([CallerMemberName] string? propertyName = null)
874 {
875 PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
876 }
877}
readonly BatchedDispatcher< IReadOnlyList< DreamineThreadInfo > > _uiBatch
DreamineThreadMonitorViewModel(IDreamineThreadManager threadManager, IThreadUiDispatcher dispatcher)
DreamineThreadMonitorViewModel(IDreamineThreadManager threadManager, IThreadUiDispatcher dispatcher, TimeSpan refreshInterval)
void ApplySnapshotsOnUiThread(IReadOnlyList< IReadOnlyList< DreamineThreadInfo > > batch)