Jalui
@Jalui

Почему одно из событий сторонних потоков игнорируется?

[В коде урезаны методы и поля, не относящиеся к делу]

Model.cs

...
namespace Timetronome
{
    public class Model : INotifyPropertyChanged
    {
        ...
        private string toFilePath = "click.wav";

        Thread timerThread;
        Thread clickerThread;

        MediaPlayer mediaPlayer;
        MediaPlayer mediaChecker;

        public Model (int receivedTempo, int receivedTime)
        {
            SettedTempo = receivedTempo;
            SettedTime = receivedTime;

            clickerThread = new Thread(new ThreadStart(Clicker));
            timerThread = new Thread(new ThreadStart(Timer));

            //CheckMediaFile(toFilePath);

            clickerThread.Start();
            timerThread.Start();
        }

        public int EstimateTime
        {
            get => estimateTime;
            private set
            {
                estimateTime = value;
                OnPropertyChanged();
            }
        }

        public bool IsMediaFailed
        {
            get => isMediaFailed;
            private set
            {
                isMediaFailed = value;
                OnPropertyChanged();
            }
        }

        public void ToogleMetronomeState(int receivedTempo, int receivedTime)
        {
            //CloseMediaChecker();

            if (!IsMetronomeRunned)
            {
                SettedTempo = receivedTempo;
                SettedTime = receivedTime;

                RunMetronome();
            }
            else
            {
                StopMetronome();
            }
        }

        private void Clicker()
        {
            mediaPlayer = new MediaPlayer();
            mediaPlayer.MediaFailed += NotifyMediaFailed;
            mediaPlayer.Open(new Uri(toFilePath, UriKind.Relative));

            int delay;

            while (!IsClosingApp)
            {
                ThreadWaiting();

                delay = 60000 / SettedTempo;

                while (!IsClosingApp && IsMetronomeRunned)
                {
                    mediaPlayer.Stop();
                    mediaPlayer.Play();

                    ThreadDelay(delay);
                }
            }

            mediaPlayer.Close();
        }

        private void Timer()
        {
            while (!IsClosingApp)
            {
                ThreadWaiting();

                EstimateTime = SettedTime;

                while (!IsClosingApp && IsMetronomeRunned && (EstimateTime > 0))
                {
                    ThreadDelay(60000);

                    EstimateTime--;
                }

                IsMetronomeRunned = false;
            }
        }

        private void CheckMediaFile(string toFilePath)
        {
            mediaChecker = new MediaPlayer();

            mediaChecker.MediaFailed += NotifyMediaFailed;

            mediaChecker.Open(new Uri(toFilePath, UriKind.Relative));
        }

        private void CloseMediaChecker() => mediaChecker?.Close();

        private void NotifyMediaFailed(object sender, ExceptionEventArgs e) => IsMediaFailed = true;

        public event PropertyChangedEventHandler PropertyChanged;
        private void OnPropertyChanged([CallerMemberName] string propertyName = "") => PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));

        private bool IsClosingApp [...]
        public bool IsMetronomeRunned [...]
        public int SettedTempo [...]
        public int SettedTime [...]
        private void RunMetronome() [...]
        private void StopMetronome() [...]
        private void ThreadDelay(int delay) [...]
        private void ThreadWaiting() [...]
        private void ThreadInterrupt(Thread threadVariable) [...]
        public void CloseApp() [...]
    }
}


ViewModel.cs

...
namespace Timetronome
{
    public class ViewModel : INotifyPropertyChanged
    {
        ...
        public ViewModel(string fromViewTempo, string fromViewTime)
        {
            FromViewTempo = ParseString(fromViewTempo, FromViewTempo);
            FromViewTime = ParseString(fromViewTime, FromViewTime);

            model = new Model(FromViewTempo, FromViewTime);
            model.PropertyChanged += ModelNotify;
        }

        public int EstimateTime { get => model.EstimateTime; }

        public bool IsMediaFailed { get => model.IsMediaFailed; }

        private void ModelNotify(object sender, PropertyChangedEventArgs e)
        {
            OnPropertyChanged(e.PropertyName);
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged(string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private int FromViewTempo [...]
        private int FromViewTime [...]
        public bool IsMetronomeRunned [...]
        public int SettedTempo [...]
        public int SettedTime [...]
        private int ParseString(string inputString, int previousIntVariableValue) [...]
        public void ToggleMetronomeState(string fromViewTempo, string fromViewTime) [...]
        public void CloseApp() [...]
    }
}


MainWindow.xaml.cs

...
namespace Timetronome
{
    public partial class MainWindow : Window, INotifyPropertyChanged
    {
        ...
        public MainWindow()
        {
            InitializeComponent();
            
            StartStopButtonText = "Start";

            viewModel = new ViewModel("120", "5");
            DataContext = viewModel;

            this.Closing += MainWindowClosing;
            viewModel.PropertyChanged += ViewModelNotify;
        }

        public string StartStopButtonText
        {
            get => startStopButtonText;
            private set
            {
                startStopButtonText = value;
                OnPropertyChanged();
            }
        }

        private void ViewModelNotify(object sender, PropertyChangedEventArgs e)
        {
            if (e.PropertyName == "IsMetronomeRunned" || e.PropertyName == "EstimateTime")
                ChangeStartStopButtonText();

            if (e.PropertyName == "IsMediaFailed" && viewModel.IsMediaFailed)
                ShowMediaFailedMessage();
        }

        private void ChangeStartStopButtonText()
        {
            if (viewModel.IsMetronomeRunned)
                StartStopButtonText = "Est. time:" + Environment.NewLine + viewModel.EstimateTime + " min";
            else
                StartStopButtonText = "Start";
        }

        private void ShowMediaFailedMessage()
        {
            MessageBox.Show("click.wav is absent or broken!");
        }

        public event PropertyChangedEventHandler PropertyChanged;

        private void OnPropertyChanged ([CallerMemberName] string propertyName = "")
        {
            PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
        }

        private void StartStopButtonClick(object sender, RoutedEventArgs e) [...]
        private void MainWindowClosing(object sender, System.ComponentModel.CancelEventArgs e) [...]
    }
}


MainWindow.xaml

<Window
        x:Name="MainWindowClass"
        ...

    <Grid>
        ...
        <Button x:Name="StartStopButton" Grid.Column="2" Grid.Row="0" Grid.RowSpan="2" Click="StartStopButtonClick" >
            <TextBlock x:Name="StartStopButtonTextBlock" TextWrapping="Wrap" TextAlignment="Center"
                       Text="{Binding StartStopButtonText, ElementName=MainWindowClass, UpdateSourceTrigger=PropertyChanged}"/>
        </Button>
    </Grid>
</Window>



Пытаюсь "поднять" сообщение от mediaPlayer из метода Clicker (из Model.cs в MainWindow.xaml.cs):
в потоке clickerThread подписываюсь методом NotifyMediaFailed => передаю в свойство IsMediaFailed значение true => вызываю метод OnPropertyChanged, который отправляет сообщение"наверх" (в основной поток), вместе с прочими уведомлениями.

Подобным образом работает запись в свойство EstimateTime в методе Timer:
в потоке timerThread передаю в свойство EstimateTime некоторое значение => вызываю метод OnPropertyChanged, который отправляет сообщение"наверх".

События обрабатываются одинаково вплоть до MainWindow.xaml.cs.
Но в первом случае событие где-то теряется.
Ключевое отличие в обработке на стороне MainWindow:
- отсутствие привязки непосредственно к какому-либо элементу для события IsMediaFailed;
- наличие привязки к кнопке StartStopButton через свойство StartStopButtonText для события EstimateTime.

Если раскомментировать в конструкторе класса Model метод CheckMediaFile, то вызванный им дополнительный MediaPlayer из основного потока успешно доставит сообщение.

Резюмируя вопрос: два сторонних потока ведут запись в свойства, которые обрабатываются главным потоком, но почему это не работает в одном из случаев?
  • Вопрос задан
  • 26 просмотров
Пригласить эксперта
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы