Search code examples
c#multithreadinglivecharts

The calling thread must be an STA thread because it is required by many UI components with livechart


Maybe my qustion is duplicate but actually I tried the other solutions and couldn't solve the problem. The Plot form is not my main form, but it opens from main form. in the main program, i have the [STAThread] before Main(). I want to run Ploting() priodically to update the plot if any changes in the data in sql table happens. my code is as follows:

namespace MainProject1{
  public partial class Plot : Form
{
    System.Timers.Timer timer = new System.Timers.Timer();
    BackgroundWorker PlotWorker;  

    public Plot()
    {
        InitializeComponent();
        timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);

        PlotWorker = new BackgroundWorker();
        PlotWorker.DoWork += new DoWorkEventHandler(PlotWorker_DoWork);
        PlotWorker.WorkerSupportsCancellation = true;

        //Ploting(); //here it works
    }

    void PlotWorker_DoWork(object sender, DoWorkEventArgs e)
    {

        while (true)
        {
            //Ploting(); //here also receive the error.
            if (PlotWorker.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
        }
    }

    private void timer_Elapsed(object sender, ElapsedEventArgs elapsedEventArg)
    {
        Ploting(); //here i receive the error
    }

    private void test_Click(object sender, EventArgs e)
    {
        timer.AutoReset = true;
        timer.Interval = 100;
        timer.Start();
    }

    private void Ploting()
    {
        try
        {
            var list1 = new List<double>();
            var list2 = new List<double>();

            SqlResultTable rt = new SqlResultTable();
            rt.ReadtoPlot(84, "readingS", ref list1, ref list2); //here i read the data from sql. it works corrrect.
            cartesianChart1.Series = new SeriesCollection
            {
                new ScatterSeries
                {
                    Title = "Setpoints",
                    Values = new ChartValues<double>(list1),
                    PointGeometry = DefaultGeometries.Diamond,
                    StrokeThickness = 2,
                    Fill = Brushes.Transparent,
                },
                new LineSeries
                {
                    Title = "UUT readings",
                    Values = new ChartValues<double>(list2),
                    LineSmoothness = 0,
                    PointGeometrySize = 10
                },
            };
            cartesianChart1.DataClick += CartesianChart1OnDataClick;
        }
        catch(Exception err)
        {
            MessageBox.Show(err.Message);
        }
    }

    private void CartesianChart1OnDataClick(object sender, ChartPoint chartPoint)
    {
        MessageBox.Show("You clicked (" + chartPoint.X + "," + chartPoint.Y + ")");
    }
}

}

when I use the Ploting() method in the constructor, it works, but when I use it in worker or in timer_Elapsed I receive the error again. I know that it has something to do with thread but don't know how to solve it. I tried to create new thread in background worker or timer_Elapsed, but received another error. Sorry for the qustion. I'm new in C#!

New update: code with delegation:

  namespace MainProject1{
  public partial class Plot : Form
{
    public delegate void plotDel(); //**** new added ***
    System.Timers.Timer timer = new System.Timers.Timer();
    BackgroundWorker PlotWorker;  

    public Plot()
    {
        InitializeComponent();
        timer.Elapsed += new ElapsedEventHandler(timer_Elapsed);

        PlotWorker = new BackgroundWorker();
        PlotWorker.DoWork += new DoWorkEventHandler(PlotWorker_DoWork);
        PlotWorker.WorkerSupportsCancellation = true;

        plotDel pl1 = OnPlotNeeded; //**** new added ***
        //Ploting(); //here it works
    }

    protected virtual void OnPlotNeeded()  //**** new added ***
    {
        Ploting();
    }
    void PlotWorker_DoWork(object sender, DoWorkEventArgs e)
    {

        while (true)
        {
            //Ploting(); //here also receive the error.
            if (PlotWorker.CancellationPending)
            {
                e.Cancel = true;
                return;
            }
        }
    }

    private void timer_Elapsed(object sender, ElapsedEventArgs elapsedEventArg)
    {
        System.Threading.SynchronizationContext.Current.Post(new System.Threading.SendOrPostCallback(o => Ploting()), null); //when i add this line, it does not go to next line(no error, no action) and without it, again receive the error as before!
        OnPlotNeeded();
    }

    private void test_Click(object sender, EventArgs e)
    {
        timer.AutoReset = true;
        timer.Interval = 100;
        timer.Start();
    }

    private void Ploting()
    {
        // the inside code is same as first code
    }

    private void CartesianChart1OnDataClick(object sender, ChartPoint chartPoint)
    {
        MessageBox.Show("You clicked (" + chartPoint.X + "," + chartPoint.Y + ")");
    }
}

}


Solution

  • The problem is presumably that timer_Elapsed is not executed on the UiThread (actually that is what the exception is trying to say you). But you can delegate the call to Ploting() back to the UiThread.

    Try this variant of timer_Elapsed

    private void timer_Elapsed(object sender, ElapsedEventArgs elapsedEventArg)
    {
        System.Threading.SynchronizationContext.Current.Post(delegate { this.Ploting(); }, null);
    }
    

    And also replace all other calls to Plotting() with the delagation.

    Edit: Save the SynchronizationContext on creation in a class-variable like

    private readonly SynchronizationContext syncContext;
    
    public Plot()
    {
        InitializeComponent();
        this.syncContext = System.Threading.SynchronizationContext.Current;
        ...
    }
    

    And call Plotting() with this context like so:

    private void Timer_Elapsed(object sender, ElapsedEventArgs e) => this.DelagetePloting();
    
    private void DelagetePloting()
        => this.syncContext.Post(delegate
        {
            this.Ploting();
        }, null);
    
    private void Ploting()
    {
        // your work
    }