how to call a function on main thread (clear a datagridview) in c# with async call from cefsharp (winforms)? - dotnetcore

-1

Relatively new to C# threading and ran into a situation whereby an async call is made using cefsharp that scrapes a table from a site (using a headless call). That part works fine, however, I need to clear and repopulate a DataGridView on the main form, and get an error

    System.InvalidOperationException
  HResult=0x80131509
  Message=Cross-thread operation not valid: Control 'dataGridView1' accessed from a thread other than the thread it was created on.
  Source=System.Windows.Forms

when I try to do so. I understand the problem, but not sure how to go about it. I figured I could use an event, but have no idea where to begin. (still pretty green with C# Winforms).

I'm running a test winforms app in DotNetCore 5.0 to get this to work. Here's my single page code:

using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.Text;
using System.Windows.Forms;
using CefSharp;
using CefSharp.OffScreen;
using HtmlAgilityPack;
using HtmlDocument = HtmlAgilityPack.HtmlDocument;
using System.IO;
using static ParseWebPage.XUtilities;
using System.Threading.Tasks;

namespace ParseWebPage
{
    public partial class Form6 : Form
    {
        private ChromiumWebBrowser browser;

        public Form6()
        {
            InitializeComponent();
            var settings = new CefSettings()
            {
                //By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data
                CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache")
            };

            //Perform dependency check to make sure all relevant resources are in our output directory.
            Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);
        }


        private void PopulateDataGridView(BindingList<cRecord> cr)
        {
            dataGridView1.DataSource = null;
            dataGridView1.DataSource = cr;
        }

        private void button1_Click(object sender, EventArgs e)
        {
            // Create the offscreen Chromium browser.
            browser = new ChromiumWebBrowser(txtTestURL.Text.Trim());

            // An event that is fired when the first page is finished loading.
            // This returns to us from another thread.
            browser.LoadingStateChanged += BrowserLoadingStateChanged;
        }

        private void BrowserLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
        {
            // Check to see if loading is complete - this event is called twice, one when loading starts
            // second time when it's finished
            // (rather than an iframe within the main frame).
            if (!e.IsLoading)
            {
                // Remove the load event handler, because we only want one snapshot of the initial page.
                browser.LoadingStateChanged -= BrowserLoadingStateChanged;

                var task2 = browser.GetSourceAsync();
                task2.ContinueWith(x =>
                {
                    //need to parse data here
                    ProcessScrapedRawPageData(task2.Result); //problem  - this is running on another thread - need an event to show this is done - fire an event or something
                }, TaskScheduler.Default);

            }
        }

        void ProcessScrapedRawPageData(string pagedata)
        {
            var doc = new HtmlDocument();
            doc.LoadHtml(pagedata);

            HtmlNode table = doc.DocumentNode.SelectSingleNode("//table[starts-with(@class, 'tbl1')]");
            HtmlNodeCollection rows = table.SelectNodes(".//tr");
            if (rows != null)
            {
                if (rows.Count > 0)
                {
                    BindingList<cRecord> cRecords = new BindingList<cRecord>();
                    foreach (HtmlNode row in rows)
                    {
                        HtmlNodeCollection cells = row.SelectNodes(".//td");
                        if (cells != null)
                        {
                            if (cells.Count == 7)
                            {
                                int i = 0;
                                cRecord r = new cRecord();
                                foreach (HtmlNode cell in cells)
                                {
                                    string cellval = cell.InnerText;
                                    i++;
                                    switch (i)
                                    {
                                        case 1:
                                            r.MarketDate = Convert.ToDateTime(cellval);
                                            break;
                                        case 2:
                                            r.Open = StrUtils.StrToDec(cellval);
                                            break;
                                        case 3:
                                            r.High = StrUtils.StrToDec(cellval);
                                            break;
                                        case 4:
                                            r.Low = StrUtils.StrToDec(cellval);
                                            break;
                                        case 5:
                                            r.Close = StrUtils.StrToDec(cellval);
                                            break;
                                        case 6:
                                            r.Volume = StrUtils.StrToInt64(cellval);
                                            break;
                                        case 7:
                                            r.MarketCap = StrUtils.StrToInt64(cellval);
                                            break;
                                        default:
                                            break;
                                    }
                                }
                                cRecords.Add(r);
                            }
                        }
                    }
                    // we have all the records -- need to update the DataGridView with the results 
                    // problem is this is on another thread, so an error occurs (need to do this on the main thread)
                    ClearDataGridView();

                    PopulateDataGridView(cRecords);
                }
            }

        }

        private void ClearDataGridView()
        {
            if (dataGridView1 != null)
            {
                if (dataGridView1.Rows != null)
                {
                    dataGridView1.Rows.Clear();
                }
            }
        }
    }
}

-- UPDATE ----------------------------------------------------------------- Okay, I tried to create an eventhandler that is invoked call OnScrapeComplete as so:

  public event EventHandler<string> OnScrapeComplete;

then changed the method to invoke it as so:

    private void BrowserLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
    {
        this.OnScrapeComplete += OnScrape_Complete;
        if (!e.IsLoading)
        {
            browser.LoadingStateChanged -= BrowserLoadingStateChanged;

            var task2 = browser.GetSourceAsync();
            task2.ContinueWith(x =>
            {
                OnScrapeComplete?.Invoke(this, task2.Result);
            }, TaskScheduler.Default);
        }
    }

    private void OnScrape_Complete(object sender, string e)
    {
        ProcessScrapedRawPageData(e);
    }

But no joy. I have the exact same problem: The ProcessScrapedRawPageData is still executing on the other thread, so when it calls either ClearDataGridView() OR PopulateDataGridView(cRecords), I still get the 'Cross-thread operation not valid: Control 'dataGridView1' accessed ... message. UGH! I'm stumped. I have no idea how to get the data to the main thread!

c#
multithreading
winforms
asynchronous
cefsharp
asked on Stack Overflow May 14, 2021 by MC9000 • edited May 15, 2021 by MC9000

0 Answers

Nobody has answered this question yet.


User contributions licensed under CC BY-SA 3.0