Pre loader

System.InvalidOperationException: 'The calling thread cannot access this object because a different thread owns it.'

Welcome to the SciChart Forums!

  • Please read our Question Asking Guidelines for how to format a good question
  • Some reputation is required to post answers. Get up-voted to avoid the spam filter!
  • We welcome community answers and upvotes. Every Q&A improves SciChart for everyone

WPF Forums | JavaScript Forums | Android Forums | iOS Forums

0
0

Hello.
Thanks for previous answer but I can’t check how it work because of new problem. I develop WPF application and try to use MVVM as architecture pattern. In the Model part of my application I used a loop started in the new thread. In that loop I get some data which received to ViewModel and used as chart data. Looks as usual.
But then I tried to set visible range updated object which bound to axis I get exception ‘System.InvalidOperationException: ‘The calling thread cannot access this object because a different thread owns it.’ in my loop. I know it happens in WPF applications and need to use Dispatcher to solve it. But as I know if you try to use MVVM you not needed to use Dispatcher often because WPF-binding in most cases is thread-safe. That happend in my case? How to solve it?
Parts of my code:

XAML

<Window x:Class="HMI.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:s="http://schemas.abtsoftware.co.uk/scichart"
        xmlns:localVM="clr-namespace:MyCom.HMI.ViewModel"
        mc:Ignorable="d"
        Title="MainWindow" Height="450" Width="800">
    <Window.DataContext>
        <localVM:HMIViewModel/>
    </Window.DataContext>
    <Window.Resources>
        <localVM:InverseBooleanConverter x:Key="InverseBooleanConverter"/>
        <localVM:ProcessStateToColorConverter x:Key="ProcessStateToColorConverter"/>
        <localVM:ProcessStateToStartedConverter x:Key="ProcessStateToStartedConverter"/>
        <localVM:ProcessStateToStoppedConverter x:Key="ProcessStateToStoppedConverter"/>
    </Window.Resources>
    <DockPanel>
        <StackPanel DockPanel.Dock="Top" Orientation="Horizontal">
            <StackPanel DockPanel.Dock="Left" Orientation="Vertical" Width="520">
                <Border DockPanel.Dock="Top" Margin="5" Padding="5" BorderBrush="Black" BorderThickness="1,1,1,1" CornerRadius="5">
                    <StackPanel DockPanel.Dock="left" Orientation="Horizontal" Height="40">
                        <Ellipse Margin="5" Height="20" Width="20" HorizontalAlignment="Left" VerticalAlignment="Center" Stroke="Black" Fill="{Binding ProcessState, Converter={StaticResource ProcessStateToColorConverter}}"/>
                        <Label Content="State" Margin="5" Width="90" HorizontalContentAlignment="Left" VerticalContentAlignment="Center"/>
                    </StackPanel>
                </Border>
                <Border DockPanel.Dock="Top" Margin="5" Padding="5" BorderBrush="Black" BorderThickness="1,1,1,1" CornerRadius="5">
                    <Grid DockPanel.Dock="Left" Height="300">
                        <s:SciChartSurface ChartTitle="Profil" RenderableSeries="{s:SeriesBinding RenderableSeries}">
                            <s:SciChartSurface.XAxis>
                                <s:NumericAxis AxisTitle="Position, m" VisibleRange="{Binding XVisibleRange, Mode=TwoWay}"/>
                            </s:SciChartSurface.XAxis>
                            <s:SciChartSurface.YAxis>
                                <s:NumericAxis AxisTitle="Size, um" VisibleRange="{Binding YVisibleRange, Mode=TwoWay}"/>
                            </s:SciChartSurface.YAxis>
                        </s:SciChartSurface>
                    </Grid>
                </Border>
            </StackPanel>
...

ViewModel

namespace MyCom.HMI.ViewModel
{
    public class HMIViewModel : BindableBase
    {      
        private readonly HMIModel _model = new();

        public HMIViewModel()
        {
            _renderableSeries = new ObservableCollection<IRenderableSeriesViewModel>();

            ((INotifyCollectionChanged)_model.TestControlData).CollectionChanged += (s, e) =>  { TestControlDataChanged(e); };

            InitCharts();
        }

...

        private ObservableCollection<IRenderableSeriesViewModel> _renderableSeries;
        public ObservableCollection<IRenderableSeriesViewModel> RenderableSeries
        {
            get { return _renderableSeries; }
            set
            {
                SetProperty(ref _renderableSeries, value, nameof(RenderableSeries));
            }
        }

        private IRange _xVisibleRange;
        public IRange XVisibleRange
        {
            get => _xVisibleRange;
            set
            {
                if (_xVisibleRange != value)
                {
                    SetProperty(ref _xVisibleRange, value, nameof(XVisibleRange));
                }
            }
        }

        private IRange _yVisibleRange;
        public IRange YVisibleRange
        {
            get => _yVisibleRange;
            set
            {
                if (_yVisibleRange != value)
                {
                    SetProperty(ref _yVisibleRange, value, nameof(YVisibleRange));
                }
            }
        }

        private XyDataSeries<double, double> _lineDataDiameter1;

...

        private void InitCharts()
        { // TODO
            _lineDataDiameter1 = new XyDataSeries<double, double>()
            { 
                SeriesName = "Diameter1"
            };

            RenderableSeries.Add(new LineRenderableSeriesViewModel()
            {
                StrokeThickness = 2,
                Stroke = Colors.SteelBlue,
                DataSeries = _lineDataDiameter1,
                StyleKey = "LineSeriesStyle"
            });
        }

        private void TestControlDataChanged(NotifyCollectionChangedEventArgs args)
        {
            if (args.Action == NotifyCollectionChangedAction.Add && args.NewItems?.Count > 0)
            {
                var testControlActualState = args.NewItems.Cast<TestControlActualState>();

                List<double> xValues = new();
                List<double> yValuesDiameter1 = new();

                foreach (var item in testControlActualState) 
                {
                    if (item.Diameter1 > 0f)
                    {
                        xValues.Add(item.FiberLength);
                        yValuesDiameter1.Add(item.Diameter1);
                    }
                }

                _lineDataDiameter1.Append(xValues, yValuesDiameter1);

                // TODO
                if (xValues.Count > 0)
                {
                    var cuurMaxValueX = xValues.Max();
                    XVisibleRange.Max = cuurMaxValueX;
                    XVisibleRange.Min = cuurMaxValueX - 7000f > 0 ? cuurMaxValueX - 7000f : 0;
                }

                // TODO
                if (yValuesDiameter1.Count > 0)
                {
                    var cuurMaxValueY = yValuesDiameter1.Max();
                    YVisibleRange.Max = cuurMaxValueY + 50;
                    YVisibleRange.Min = 0;
                }
            }
        }

Model

namespace MyCom.HMI.Model
{
    public class HMIModel : BindableBase, IDisposable
    {
        private readonly ObservableCollection<TestControlActualState> _testControlData;
        internal ReadOnlyObservableCollection<TestControlActualState> TestControlData { get; }

        public HMIModel()
        {
            _testControlData = new ObservableCollection<TestControlActualState>();
            TestControlData = new ReadOnlyObservableCollection<TestControlActualState>(_testControlData);
        }

...

        private void StartPollerThread()
        {
            _pollerCancellationToken ??= new CancellationTokenSource();

            if (!_pollerCancellationToken.IsCancellationRequested)
            {
                Task.Factory.StartNew(() => PollerDoWork(_pollerCancellationToken.Token), TaskCreationOptions.LongRunning);
            }
        }

        private void PollerDoWork(CancellationToken cancellationToken)
        {
            try
            {
                Thread.CurrentThread.Priority = ThreadPriority.Lowest;
                IsPollerStarted = true;

                while (!cancellationToken.IsCancellationRequested && (_isKeepConnection || _countOfTrying <= MAX_COUNT_OF_TRYING))
                {
                    try
                    {
                        _testControlData.Add(_emulator.GetTestControlActualState());

                        if (!_isKeepConnection && _countOfTrying > 0)
                        {
                            Thread.Sleep(_pollerTimeout / REASK_COEFICIENT);
                            continue;
                        }
                    }
                    catch
                    {
                        // Thread must be alive!!! ...ALWAYS!!!
                    }
                    Thread.Sleep(_pollerTimeout);
                }
            }
            finally
            {
                IsPollerStarted = false;
                _pollerCancellationToken = null;
            }
        }
Version
8.1.0.27856
  • Fedor Iudin
    When ViewModel line 98 executed I have error on Model’s line 46
  • You must to post comments
0
0

Hi there

To debug this we would need a small stand alone solution to reproduce it.

One thing I can say, in line #98 of your ViewModel you have this:

XVisibleRange.Max = cuurMaxValueX;
XVisibleRange.Min = cuurMaxValueX - 7000f > 0 ? cuurMaxValueX - 7000f : 0;

While this works its not advisable, it is best practice to update the entire DoubleRange object, e.g.

var min = cuurMaxValueX;
var max = cuurMaxValueX - 7000f > 0 ? cuurMaxValueX - 7000f : 0;
XVisibleRange = new DoubleRange(min, max); // Assuming XVisibleRange raises PropertyChanged

This will send a single INotifyPropertyChanged.PropertyChanged event to the XAxis.VisibleRange property and in WPF / MVVM all PropertyChanged events are marshalled to the UI thread

Whereas, when updating/setting XVisibleRange.Max there is an internal event or callback which monitors this however it is not marshalled to the UI thread.

Try that. If you still have problems, then please send a code sample (minimal solution) to reproduce

Best regards
Andrew

  • You must to post comments
Showing 1 result
Your Answer

Please first to submit.