Live update stocks and prices from stocks markets

Collapse
X
 
  • Time
  • Show
Clear All
new posts
  • davidmichaeli
    New Member
    • Sep 2022
    • 1

    Live update stocks and prices from stocks markets

    I've wpf application, where I load large data (into Wpf DataGrid) and handle multiple events per 1-5 seconds..

    I have a performance issue with datagrid - rendering takes too long and freezes my app until it's all rendered.

    QuestionTester. View.QuestionVi ew:

    Code:
    <UserControl x:Class="QuestionTester.View.QuestionView"
                 xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
                 xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
                 xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006" 
                 xmlns:d="http://schemas.microsoft.com/expression/blend/2008" 
                 xmlns:local="clr-namespace:QuestionTester.View"
                 mc:Ignorable="d"
                 xmlns:Converters="clr-namespace:QuestionTester.Converters"
                 d:DesignHeight="450" d:DesignWidth="800">
        <UserControl.Resources>
            <Converters:FromDateConverter x:Key="FromDateConv"/>
            <Converters:ToDateConverter x:Key="ToDateConv"/>
            <Converters:ToTimeConverter x:Key="ToTimeConv"/>
        </UserControl.Resources>
        <Grid>
            <Grid.RowDefinitions>
                <RowDefinition Height="20"/>
                <RowDefinition Height="*"/>
            </Grid.RowDefinitions>
            <TextBlock Grid.Row="0" Text="{Binding RecordsCount}"/>
            <DataGrid
                Grid.Row="1"
                ItemsSource="{Binding Models}"
                IsReadOnly="True" 
                AutoGenerateColumns="False" 
                SnapsToDevicePixels="False"
                VirtualizingStackPanel.IsVirtualizing="True"
                VirtualizingStackPanel.VirtualizationMode="Recycling" 
                EnableRowVirtualization="True" 
                MaxWidth="2560" 
                MaxHeight="1600">
                <DataGrid.Columns>
                    <DataGridTemplateColumn Header="CreationTime" ToolTipService.ToolTip="CreationTime">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <StackPanel Margin="4,0,0,0"  FlowDirection="LeftToRight"  VerticalAlignment="Center">
                                    <ToolTipService.ToolTip>
                                        <TextBlock Text="{Binding Path=CreationTime, Converter={StaticResource ToTimeConv}}"/>
                                    </ToolTipService.ToolTip>
                                    <TextBlock HorizontalAlignment="Left" Text="{Binding Path=CreationTime, Converter={StaticResource ToTimeConv}}"/>
                                </StackPanel>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn Header="StartDate" ToolTipService.ToolTip="StartDate">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <StackPanel Margin="4,0,0,0"  FlowDirection="LeftToRight"  VerticalAlignment="Center">
                                    <ToolTipService.ToolTip>
                                        <TextBlock Text="{Binding Path=StartDate, Converter={StaticResource FromDateConv}}"/>
                                    </ToolTipService.ToolTip>
                                    <TextBlock HorizontalAlignment="Left" Text="{Binding Path=StartDate, Converter={StaticResource FromDateConv}}"/>
                                </StackPanel>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTemplateColumn Header="EndDate" ToolTipService.ToolTip="EndDate">
                        <DataGridTemplateColumn.CellTemplate>
                            <DataTemplate>
                                <StackPanel Margin="4,0,0,0"  FlowDirection="LeftToRight"  VerticalAlignment="Center">
                                    <ToolTipService.ToolTip>
                                        <TextBlock Text="{Binding Path=EndDate, Converter={StaticResource ToDateConv}}"/>
                                    </ToolTipService.ToolTip>
                                    <TextBlock HorizontalAlignment="Left" Text="{Binding Path=EndDate, Converter={StaticResource ToDateConv}}"/>
                                </StackPanel>
                            </DataTemplate>
                        </DataGridTemplateColumn.CellTemplate>
                    </DataGridTemplateColumn>
                    <DataGridTextColumn Header="Guid" Binding="{Binding Path=Guid}"/>
                    <DataGridTextColumn Header="Key" Binding="{Binding Path=Key}"/>
                    <DataGridTextColumn Header="Field" Binding="{Binding Path=Field}"/>
                    <DataGridTextColumn Header="Field1" Binding="{Binding Path=Field1}"/>
                    <DataGridTextColumn Header="Field2" Binding="{Binding Path=Field2}"/>
                </DataGrid.Columns>
            </DataGrid>
        </Grid>
    </UserControl>
    Code:
    public partial class QuestionView : UserControl
    {
        QuestionViewModel _vm;
        public QuestionView()
        {
            InitializeComponent();
            _vm = new QuestionViewModel(TaskScheduler.FromCurrentSynchronizationContext());
            DataContext = _vm;
        }
    }
    QuestionModel:

    Code:
     public class QuestionModel
    {
        public static Random _rand = new Random();
        public DateTime StartDate { get; set; }
        public DateTime EndDate { get; set; }
        public DateTime CreationTime{ get; set; }
        public Guid Guid { get; set; }
        public string Key { get; set; }
        public int Field { get; set; }
        public long Field1 { get; set; }
        public double Field2 { get; set; }
        public QuestionModel(int num)
        {
            Key = $"key_{num}";
            Field = _rand.Next(1000000);
            Field1 = _rand.Next(1000000);
            Field2 = _rand.Next(1000000);
            Guid = Guid.NewGuid();
            CreationTime = DateTime.Now;
            if (num % 2 == 0)
            {
                StartDate = DateTime.Now.AddDays(-1);
                EndDate = DateTime.Now.AddDays(1);
            }
            if (num % 10 == 0)
            {
                StartDate = DateTime.Now.AddDays(-100);
                EndDate = DateTime.Now.AddDays(100);
            }
            else
            {
                StartDate = DateTime.Now;
                EndDate = DateTime.Now;
            }
        }
        public override string ToString()
        {
            return $"CreationTime:{CreationTime}";
        }
    }
    QuestionViewMod el:

    Code:
    public class QuestionViewModel : NotificationBase, IUpdateTotalItems, IDisposable
    {
        const int RAND = 10000000;
        static Random _rand = new Random();
        const int LENGTH = 1000;
        public BulkObservableCollection<QuestionModel> Models { get; }// = new BulkObservableCollection<QuestionModel>();
        TaskScheduler _currentcontext;
        Guid _cancelToken;
        HashSet<Guid> _cancelTokensDict = new HashSet<Guid>();
        DispatcherTimer _dispatcherUpdateTimer;
        Thread _thread;
        public QuestionViewModel(TaskScheduler currentcontext)
        {
            _currentcontext = currentcontext;
            _cancelToken = Guid.NewGuid();
            Models = new BulkObservableCollection<QuestionModel>(_cancelToken, ref _cancelTokensDict, this);
    
            _dispatcherUpdateTimer = new DispatcherTimer(DispatcherPriority.Background);
            _dispatcherUpdateTimer.Interval = TimeSpan.FromMilliseconds(1000);
            _dispatcherUpdateTimer.Tick += _dispatcherUpdateTimer_Tick;
            _dispatcherUpdateTimer.Start();
            QuestionModel[] array = new QuestionModel[LENGTH];
            for (int i = 0; i < LENGTH; i++)
            {
                int indx = _rand.Next(RAND);
                array[i] = new QuestionModel(indx);
            }
            Models.AddRange(array);
            Models.LoadingFinished();
            _thread = new Thread(() =>
            {
                while (!_cancelTokensDict.Contains(_cancelToken))
                {
                    QuestionModel[] arrayThread = new QuestionModel[LENGTH];
                    for (int i = 0; i < LENGTH; i++)
                    {
                        int indx = _rand.Next(RAND);
                        arrayThread[i] = new QuestionModel(indx);
                    }
                    Task.Factory.StartNew(() =>
                    {
                        try
                        {
                            for (int i = 0; i < arrayThread.Length; i++)
                            {
                                Models.Enqueue(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Add, arrayThread[i], 0));
                            }
                           // Models.AddRange(arrayThread);
                        }
                        catch (Exception)
                        {
                            throw;
                        }
                    }, CancellationToken.None, TaskCreationOptions.None, _currentcontext);
                    Thread.Sleep(1000);
                }
            });
            _thread.IsBackground = true;
            _thread.Start();
        }
    
    
        private int Comparison(QuestionModel a, QuestionModel b)
        {
            if (a == null)
                return 1;
            if (b == null)
                return -1;
            var comprand = b.CreationTime.CompareTo(a.CreationTime);
            return comprand;
        }
    
        private void _dispatcherUpdateTimer_Tick(object sender, EventArgs e)
        {
            if (Models.IsLoading)
                return;
    
            Models.Refresh();
        }
    
        int _recordsCound;
        public int RecordsCount
        {
            get => _recordsCound;
            set
            {
                if (_recordsCound != value)
                {
                    _recordsCound = value;
                    OnPropertyChanged("RecordsCount");
                }
            }
        }
    
        public void UpdateTotalItems(int count)
        {
            RecordsCount = count;
        }
    
        bool _isDisposed = false;
        public void Dispose()
        {
            if (!_isDisposed)
            {
                _isDisposed = true;
                lock (_cancelTokensDict)
                {
                    _cancelTokensDict.Add(_cancelToken);
                }
                if (_thread != null)
                {
                    try
                    {
                        _thread.Join();
                    }
                    catch
                    {
                    }
                    _thread = null;
                }
                if (_dispatcherUpdateTimer != null)
                {
                    _dispatcherUpdateTimer.Stop();
                    _dispatcherUpdateTimer.Tick -= _dispatcherUpdateTimer_Tick;
                    _dispatcherUpdateTimer = null;
                }
            }
        }
    }
    Converters:

    Code:
     public class ToTimeConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
                return null;
            DateTime? date = (DateTime)value;
            if (date.HasValue)
            {
                return date.Value.ToString("HH:mm:ss:fff");
            }
            return null;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    public class ToDateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
                return null;
            DateTime? date = (DateTime)value;
            if (date.HasValue)
            {
                if (date.Value.Date == DateTime.Now.Date)
                {
                    return "Today";
                }
                else if (date.Value.Date.Date == DateTime.Now.Date.AddDays(1))
                {
                    return "Tomorrow";
                }
                return date.Value.Date.ToShortDateString();
            }
            return null;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    public class FromDateConverter : IValueConverter
    {
        public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
        {
            if (value == null)
                return null;
            DateTime? date = (DateTime)value;
            if (date.HasValue)
            {
                if (date.Value.Date == DateTime.Now.Date)
                {
                    return "Today";
                }
                else if (date.Value.Date.Date == DateTime.Now.Date.AddDays(-1))
                {
                    return "Yesterday";
                }
                return date.Value.Date.ToShortDateString();
            }
            return null;
        }
    
        public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
        {
            throw new NotImplementedException();
        }
    }
    BulkObservableC ollection:

    Code:
      public class BulkObservableCollection<T> : IDisposable, INotifyCollectionChanged, INotifyPropertyChanged, IEnumerable<T>
    {
        ConcurrentQueue<NotifyCollectionChangedEventArgs> _losts = new ConcurrentQueue<NotifyCollectionChangedEventArgs>();
        Guid? _cancelToken = null;
        private HashSet<Guid> _cancelTokensDict = null;
        bool _isInAddRange = false;
        bool _isLoading = true;
        public bool IsLoading
        {
            get
            {
                lock (_locker)
                {
                    return _isLoading;
                }
            }
        }
        const string COUNT = "Count";
        const string ITEMS = "Item[]";
        public event NotifyCollectionChangedEventHandler CollectionChanged;
        public event PropertyChangedEventHandler PropertyChanged;
        List<T> _items = new List<T>();
        IUpdateTotalItems _updateTotalItems = null;
        public int Count => _items.Count;
    
        public BulkObservableCollection()
        {
        }
    
        public BulkObservableCollection(Guid cancelToken, ref HashSet<Guid> cancelTokensDict, IUpdateTotalItems updateTotalItems = null)
        {
            _cancelToken = cancelToken;
            _cancelTokensDict = cancelTokensDict;
            _updateTotalItems = updateTotalItems;
        }
    
        readonly object _locker = new object();
        public void LoadingFinished()
        {
            lock (_locker)
            {
                _isLoading = false;
                BeginUpdate();
            }
        }
    
        public void Refresh()
        {
            EndUpdate();
            UpdateTotalItems(_items.Count);
            BeginUpdate();
        }
    
        #region INotifyPropertyChanged
    
        private void OnPropertyChanged(string propertyName)
        {
            if (_isDispose)
                return;
            if (!_isInAddRange)
            {
                try
                {
                    var ev = this.PropertyChanged;
                    if (ev != null)
                    {
                        ev(this, new PropertyChangedEventArgs(propertyName));
                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine($"OnPropertyChanged:: Error on FirePropertyChanged propertyName = {propertyName}, Error: {ex.Message}");
                }
            }
        }
    
        #endregion INotifyPropertyChanged
    
        #region INotifyCollectionChanged
    
        private void OnCollectionChanged(NotifyCollectionChangedAction action, object item, int index)
        {
            if (_isDispose)
                return;
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(action, item, index));
        }
    
        private void OnCollectionChanged(NotifyCollectionChangedEventArgs e)
        {
            if (_isDispose)
                return;
            if (!_isInAddRange)
            {
                try
                {
                    var ev = this.CollectionChanged;
                    if (ev != null)
                    {
                        ev(this, e);
                    }
                }
                catch (Exception ex)
                {
                    Trace.WriteLine($"OnPropertyChanged:: Error on CollectionChanged Error: {ex.Message}");
                }
            }
        }
    
        #endregion INotifyCollectionChanged
    
        #region Add 
    
        public void Add(T item)
        {
            if (_isDispose)
                return;
            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                return;
           // int index = _items.Count;
            InsertItem(0, item);
        }
    
        public void Insert(int index, T item)
        {
            if (_isDispose)
                return;
            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                return;
            InsertItem(index, item);
        }
    
        private void InsertItem(int index, T item)
        {
            if (_isDispose)
                return;
            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                return;
            _items.Insert(index, item);
            OnPropertyChanged(COUNT);
            OnPropertyChanged(ITEMS);
            OnCollectionChanged(NotifyCollectionChangedAction.Add, item, index);
        }
    
        public void AddRange(T[] array)
        {
            if (_isDispose)
                return;
            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                return;
            if (array == null)
                throw new ArgumentNullException("array");           
            BeginUpdate();
            for (int i = 0; i < array.Length; i++)
            {
                if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                    break;
                var item = array[i];
                if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                    break;
                Add(item);
            }
            EndUpdate();
        }
    
        public void AddRange(List<T> list)
        {
            if (_isDispose)
                return;
            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                return;
            if (list == null)
                throw new ArgumentNullException("list");
    
            BeginUpdate();
            for (int i = 0; i < list.Count; i++)
            {
                if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                    break;
                var item = list[i];
                if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                    break;
                Add(item);
            }
            EndUpdate();
        }
    
        #endregion Add 
    
        #region Remove
    
        public bool Remove(T item)
        {
            if (_isDispose)
                return false;
            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                return false;
            int index = _items.IndexOf(item);
            if (index < 0) return false;
            RemoveItem(item, index);
            return true;
        }
    
        private void RemoveItem(T removedItem, int index)
        {
            _items.RemoveAt(index);
            OnPropertyChanged(COUNT);
            OnPropertyChanged(ITEMS);
            OnCollectionChanged(NotifyCollectionChangedAction.Remove, removedItem, index);
        }
    
        #endregion Remove
    
        #region Clear
    
        public void Clear()
        {
            if (_isDispose)
                return;
            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                return;
            _items.Clear();
            OnPropertyChanged(COUNT);
            OnPropertyChanged(ITEMS);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    
        #endregion Clear
    
        #region GetEnumerator
    
        public IEnumerator<T> GetEnumerator()
        {
            return _items.GetEnumerator();
        }
    
        IEnumerator IEnumerable.GetEnumerator()
        {
            return ((IEnumerable)_items).GetEnumerator();
        }
    
        #endregion GetEnumerator
    
        #region Dispose
    
        bool _isDispose = false;
        public void Dispose()
        {
            if (!_isDispose)
            {
                _isDispose = true;
                if (_cancelToken != null && _cancelTokensDict != null)
                {
                    _cancelTokensDict.Add(_cancelToken.Value);
                }
                if (_items != null)
                {
                    _items.Clear();
                }
                while (_losts.TryDequeue(out NotifyCollectionChangedEventArgs item))
                { // do nothing
                }
            }
        }
    
        #endregion Dispose
    
        public void BeginUpdate()
        {
            if (_isDispose)
                return;
            _isInAddRange = true;
        }
    
        public void EndUpdate()
        {
            if (_isDispose)
                return;
            _isInAddRange = false;
            OnPropertyChanged(COUNT);
            OnPropertyChanged(ITEMS);
            OnCollectionChanged(new NotifyCollectionChangedEventArgs(NotifyCollectionChangedAction.Reset));
        }
    
        public void UpdateTotalItems(int count)
        {
            if (_updateTotalItems != null)
            {
                _updateTotalItems.UpdateTotalItems(count);
            }
        }
    
        public void Enqueue(NotifyCollectionChangedEventArgs e)
        {
            bool sendToHandle = false;
            lock (_locker)
            {
                if (!_isLoading)
                {
                    sendToHandle = true;
                }
            }
            if (sendToHandle)
            {
                Handle(e);
            }
            else
            {
                lock (_losts)
                {
                    _losts.Enqueue(e);
                }
            }
        }
    
        public void PopulateFromLost()
        {
            if (_isDispose)
                return;
            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                return;
    
            lock (_losts)
            {
                while (_losts.TryDequeue(out NotifyCollectionChangedEventArgs e))
                {
                    if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                        break;
                    Handle(e);
                }
            }
        }
    
        private void Handle(NotifyCollectionChangedEventArgs e)
        {
            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                return;
            switch (e.Action)
            {
                case NotifyCollectionChangedAction.Add:
                    {
                        foreach (var item in e.NewItems)
                        {
                            try
                            {
                                if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                                    break;
    
                                _items.Insert(e.NewStartingIndex, (T)item);
                            }
                            catch (Exception ex)
                            {
                                Trace.WriteLine($"Handle:Add, e.NewStartingIndex:{e.NewStartingIndex}, Error:{ex.Message}");
                            }
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Remove:
                    {
                        foreach (var item in e.OldItems)
                        {
                            try
                            {
                                if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                                    break;
    
                                _items.Remove((T)item);
                            }
                            catch (Exception ex)
                            {
                                Trace.WriteLine($"Handle:Remove, Error:{ex.Message}");
                            }
                        }
                    }
                    break;
                case NotifyCollectionChangedAction.Move:
                    {
                        try
                        {
                            if (_cancelTokensDict != null && _cancelToken.HasValue && _cancelTokensDict.Contains(_cancelToken.Value))
                                break;
    
                            var itemMoved = (T)e.OldItems[0];
                            _items.Remove(itemMoved);
                            _items.Insert(e.NewStartingIndex, itemMoved);
                        }
                        catch (Exception ex)
                        {
                            Trace.WriteLine($"Handle:Move, e.OldStartingIndex:{e.OldStartingIndex}, e.NewStartingIndex:{e.NewStartingIndex}, Error:{ex.Message}");
                        }
                    }
                    break;
            }
        }
    }
Working...