Многопоточность в в C# и WPF

84
25 июня 2021, 11:00

Как реализовать многопоточность в WPF? Нужно сделать программу, которая при поступлении данных на COM порт меняет видео в программе на другое, случайно выбранное из доступных. Суть в том, что бы запустить во втором потоке цикл, который будет читать данные с com порта и менять видео в программе, т.к. когда я просто вхожу в цикл программа просто крашается.

Вот мой код:

OptionsWindow optionsWindow;
    SerialPort myPort;
    int timer;
    public MainWindow()
    {
        InitializeComponent();
        myPort = new SerialPort();
        playerMediaElement.Source = new Uri(AppDomain.CurrentDomain.BaseDirectory + @"Videos\0.mp4", UriKind.Absolute);
        playerMediaElement.LoadedBehavior = MediaState.Manual;
        playerMediaElement.UnloadedBehavior = MediaState.Manual;
        playerMediaElement.Play();
        using (StreamReader sr = new StreamReader("portOptions.txt", Encoding.Default))
        {
            myPort.PortName = sr.ReadLine();
            myPort.BaudRate = int.Parse(sr.ReadLine());
            timer = int.Parse(sr.ReadLine());
        }
    }
    private void PlayerMediaElement_MediaEnded(object sender, RoutedEventArgs e)
    {
        playerMediaElement.Stop();
        playerMediaElement.Play();
    }
    private void Window_KeyDown(object sender, KeyEventArgs e)
    {
        if(e.Key == Key.P)
        {
            optionsWindow = new OptionsWindow();
            optionsWindow.Show();
        }
    }

Нужно выполнять эту функцию в новом потоке или асинхронно (главное, что бы программа не крашилась)

void TestOnNewLog()
    {
        while (true)
        {
            if (myPort.ReadLine() != null) MessageBox.Show("Пришли новые данные!");
        }
    }

Я искал в интернете материал на эту тему, но не помогало... Если кто то знает прошу помочь

Answer 1

Сам по себе запуск потока - задача тривиальная. Как я уже скаазал, вы примеры можете найти в документации или просто поиском в интернете.

Однако, вы должны понимать, что в WPF есть понятие главного UI потока и всё взаимодействие с элементами пользовательского интерфейса должно происходить строго через UI поток.

Возникает вопрос: вот запустили вы свой код в фоновом потоке, и вам надо что то обновить в интерфейсе программы. Как это сделать? Для этого существует диспетчер. Вы можете попросить диспетчер выполнить код в UI потоке.

Как это работает: Например, я создам поток, который посчитает до 100, и при этом будет выводить значение счетчика на экран:

public class MyWnd : Window
{
    private TextBlock tb;
    public MyWnd()
    {
        tb = new TextBlock() { Width = 300 };
        tb.FontSize = 72;
        this.Content = tb;
        this.SizeToContent = SizeToContent.WidthAndHeight;
        var thread = new Thread(Worker) { IsBackground = true };
        thread.Start();
    }

    private void Worker(object state)
    {
        for (var i = 0; i < 100; i++)
        {
            var t = i.ToString();
            this.Dispatcher.Invoke(() => { tb.Text = t; });
            Thread.Sleep(1000);
        }
    }
}

Как запустить

new MyWnd().ShowDialog();

Результат

Остается только отметить, что описанный здесь подход хорошо работает на небольших приложениях, но если вы что то более-менее серьезное пишете, то вам стоит обратить внимание на MVVM паттерн.

Вот небольшой пример. Определим простую команду

public class DelegateCommand : ICommand
{
    private readonly Action<object> _execute;
    private readonly Predicate<object> _canExecute;
    public DelegateCommand(Action<object> execute, Predicate<object> canExecute = null)
    {
        _execute = execute;
        _canExecute = canExecute;
    }
    public bool CanExecute(object parameter)
    {
        return _canExecute?.Invoke(parameter) ?? true;
    }

    public void Execute(object parameter)
    {
        _execute.Invoke(parameter);
    }
    public event EventHandler CanExecuteChanged
    {
        add => CommandManager.RequerySuggested += value;
        remove => CommandManager.RequerySuggested -= value;
    }
}

Наша ViewModel будет содежать значение и всю логику по управлению потоками

public class MyViewModel : INotifyPropertyChanged
{
    public ICommand StartCommand { get; }
    public ICommand StopCommand { get; }

    private int _value;
    public int Value
    {
        get => _value;
        set
        {
            if (value == _value) return;
            _value = value;
            OnPropertyChanged();
        }
    }
    private Thread _thread;
    private CancellationTokenSource _tokenSource;
    public MyViewModel()
    {
        StartCommand = new DelegateCommand((p) =>
            {
                _tokenSource = new CancellationTokenSource();
                _thread = new Thread(Worker) { IsBackground = true };
                _thread.Start(_tokenSource.Token);
            },
            p => _thread == null);
        StopCommand = new DelegateCommand(p =>
        {
            _tokenSource.Cancel();
            _tokenSource = null;
            _thread = null;
        }, p => _thread != null);
    }
    private void Worker(object state)
    {
        var token = (CancellationToken)state;
        while (!token.IsCancellationRequested)
        {
            Value++;
            Thread.Sleep(1000);
        }
    }
    public event PropertyChangedEventHandler PropertyChanged;
    [NotifyPropertyChangedInvocator]
    protected virtual void OnPropertyChanged([CallerMemberName] string propertyName = null)
    {
        PropertyChanged?.Invoke(this, new PropertyChangedEventArgs(propertyName));
    }
}

Тут все просто - 2 команды, на старт и стоп, и запуск / остановка потока.

Далее, представление будет вот такое

<Window x:Class="RU_1018395.MainWindow"
            xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
            xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
            xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
            xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
            xmlns:local="clr-namespace:RU_1018395"
            mc:Ignorable="d" 
            Title="MainWindow" SizeToContent="WidthAndHeight" >
    <Window.Resources>
        <local:MyViewModel x:Key="viewmodel"></local:MyViewModel>
    </Window.Resources>

    <Grid DataContext="{StaticResource viewmodel}">
        <Grid.ColumnDefinitions>
            <ColumnDefinition></ColumnDefinition>
            <ColumnDefinition></ColumnDefinition>
        </Grid.ColumnDefinitions>
        <StackPanel Orientation="Vertical" Margin="5">
            <Button Command="{Binding StartCommand}">Start</Button>
            <Button Command="{Binding StopCommand}">Stop</Button>
        </StackPanel>
        <TextBlock FontSize="72" Grid.Column="1" Text="{Binding Value}"></TextBlock>
    </Grid>
</Window>

В представлении я положил ViewModel как ресурс и далее натянул эту ViewModel на грид. Кстати, можно заметить, что я нигде не использовал диспетчер - всё потому, что я использую привязки для передачи данных между ViewModel и View, а они уже сами понимают, как рабоать с UI потоком.

Ну и результат

С одной стороны может показаться, что второй пример более сложный, но на самом деле мы отделили логику от представления и избежали мешанины, которая у вас в примере в конструкторе главной формы.

READ ALSO
Пустой NuGet пакет при скачивании

Пустой NuGet пакет при скачивании

Развернул свой Nuget Server на удаленном хостеТакже собрал два NuGet пакета и добавил их напрямую в репозиторий сервера, и выкладываю сервак вместе...

96
Как подебажить аутентификацию на ASP?

Как подебажить аутентификацию на ASP?

Пытаюсь настроить JWT аутентификацию на AspNet Core

104
Как правильно упаковать проект на гитхаб?

Как правильно упаковать проект на гитхаб?

я сделал простенькое aspnet mvc приложение используя EF Database First далее залил на гитхаб, он при скачке на другой машине он не работает, как правильно...

109