使用线程和MVVM将高速数据流式传输到WPF UI

本文关键字:传输 WPF UI 数据流 高速 线程 MVVM | 更新日期: 2023-09-27 18:06:38

我在使用一个新的WPF应用程序时遇到了一个问题,该应用程序试图在文本框中显示高速字节流。字节来自串行端口,我做了一个旧的WinForms应用程序处理流很好,有一个后台线程从串行端口读取,并通过调用一个委托发布到UI。

现在这个WPF应用程序的问题是我使用的是基本的MVVM方法。我将UI上的文本框绑定到VM上的属性,该属性基于INotifyPropertyChanged触发PropertyChanged事件。当数据准备好通过读取串行端口的服务上的订阅事件发布到ui时,我使用以下命令:

Action dispatchAction = () => { FormattedStream += s; };
_currentDispatcher.Invoke(dispatchAction);

FormattedStream是UI绑定的虚拟机字符串属性。

WPF应用程序上发生的事情在WinForms版本中没有发生的是WPF应用程序变得缓慢和无响应,因为它运行时无法跟上流以及WinForms应用程序,并且根据我的任务管理器,WPF应用程序正在使用/需要更多的处理器。

我想知道的是,是否有一些解决方案可以处理流(高速)数据到WPF UI。

ETA:我还尝试使用BeginInvoke而不是Invoke,当使用BeginInvoke时,流持续几秒钟然后冻结。调用是唯一的方法,我可以让它连续流到UI。

ETA:代码如下:

//

public partial class MainWindow : Window, IView
{
  public MainWindow()
  {
     InitializeComponent();
  }
  public IViewModel ViewModel
  {
     get { return DataContext as IViewModel; }
     set { DataContext = value; }
  }
  public void ScrollToCaret()
  {
     txtBoxOutPut.ScrollToEnd();
     if (txtBoxOutPut.Text.Length > 10000)
        txtBoxOutPut.Text = txtBoxOutPut.Text.Remove(0, 9000);
  }
  public event Action ComPortSelected;
  public event Action StartPortReader;
  public event Action StopPortReader;

  private void Start_Click(object sender, RoutedEventArgs e)
  {
     StartPortReader.Invoke();
  }
  private void Stop_Click(object sender, RoutedEventArgs e)
  {
     StopPortReader.Invoke();
  }

}

//ViewModel

public class ViewModel : IViewModel, INotifyPropertyChanged

{private readonly iseralportreaderservice _portReaderService;private readonly Dispatcher _currentDispatcher;

  public ViewModel(IView view, ISerialPortReaderService portReaderService)
  {
     View = view;
     View.ViewModel = this;
     View.StartPortReader += View_StartPortReader;
     View.StopPortReader += View_StopPortReader;
     _portReaderService = portReaderService;
     _currentDispatcher = Dispatcher.CurrentDispatcher;
     _portReaderService.ByteArrived += _portReaderService_ByteArrived;
  }
  private void _portReaderService_ByteArrived(string s)
  {
     Action dispatchAction = () => { FormattedStream = s; };
     _currentDispatcher.Invoke(dispatchAction);
  }
  private void View_StopPortReader()
  {
     _portReaderService.Stop();
  }
  private void View_StartPortReader()
  {
     _portReaderService.Start(SelectedPort);
  }
  public IView View { get; private set; }
  public void ShowView()
  {
     View.Show();
  }
  private StringBuilder _FormattedStream = new StringBuilder();
  public string FormattedStream
  {
     get
     {
        return _FormattedStream.ToString();
     }
     set
     {
        _FormattedStream.Append(value);
        PropertyChanged(this, new PropertyChangedEventArgs("FormattedStream"));
        View.ScrollToCaret();
     }
  }
  private string _SelectedPort;
  public string SelectedPort
  {
     get
     {
        return _SelectedPort;
     }
     set
     {
        _SelectedPort = value;
        PropertyChanged(this, new PropertyChangedEventArgs("SelectedPort"));
     }
  }
  public ReadOnlyCollection<string> AvailablePorts
  {
     get { return GetAvailablePorts(); }
  }
  private ReadOnlyCollection<string> GetAvailablePorts()
  {
     var ports = System.IO.Ports.SerialPort.GetPortNames();
     return new ReadOnlyCollection<string>(ports.ToList());
  }
  public event PropertyChangedEventHandler PropertyChanged = delegate { };

}

//Serial Port reader service

public class SerialPortReaderService : ISerialPortReaderService

{private SerialPort _port = new SerialPort();private readonly threadrunner _threadRunner;

  public SerialPortReaderService(IThreadRunner threadRunner)
  {
     _threadRunner = threadRunner;
  }
  public void Start(string comPort)
  {
     if (_port != null && !_port.IsOpen)
     {
        _port.PortName = comPort;
        _port.BaudRate = 4800;            
        _port.Open();
        _threadRunner.Start(() =>
                               {
                                     var b = new byte[20];
                                     var bArray = _port.Read(b, 0, 20);
                                     foreach (var b1 in b)
                                     {
                                        next10Bytes.Append(b1 + ", ");
                                     }                                        
                                     BytesArrived(next10Bytes.ToString());
                                     next10Bytes.Clear();
                                     Thread.Sleep(10);
                               });
     }
  }
  private StringBuilder next10Bytes = new StringBuilder();
  public void Stop()
  {
     if (_port.IsOpen)
     {
        _threadRunner.Stop();
        _port.Close();
     }
  }
  public event Action<string> BytesArrived;

}

//一个线程运行程序,我使用

public class ThreadRunner : IThreadRunner

{私有线程_thread;private bool _isRunning;

  /// <summary>
  /// Will continuously run in a while loop the action submitted in a separate thread
  /// </summary>
  /// <param name="toDoAction"></param>
  public void Start(Action toDoAction)
  {
     if (_thread != null && _thread.IsAlive)
        Stop();
     _isRunning = true;
     _thread = new Thread(() =>
                             {
                                while (_isRunning)
                                {
                                   toDoAction.Invoke();
                                }
                             });
     _thread.Start();
  }
  public void Stop()
  {
     _isRunning = false;
     if (_thread != null && _thread.IsAlive)
     {
        _thread.Abort();
        _thread.Join(new TimeSpan(0, 0, 1));
     }
  }
  public bool ThreadIsRunning
  {
     get { return _isRunning; }
  }

}

使用线程和MVVM将高速数据流式传输到WPF UI

根据Petoj提到的,我创建了一个新窗口,仍然使用流媒体服务,但只有窗口本身订阅了bytesarrived事件,并通过Dispatcher手动添加到txtbox。调用之后就解决了这个问题,没有变慢,也没有大量的CPU占用。

这个故事的寓意是,绑定或至少绑定到一个每次添加时都会不断更新的字符串会导致速度变慢。