Home > front end >  COMException: Cannot change thread mode after it is set on EnsureCoreWebView2Async(null) C#
COMException: Cannot change thread mode after it is set on EnsureCoreWebView2Async(null) C#

Time:01-05

I am writing a C# application that will be loading a technical chart webpage that is generated by an outside server API. I am just trying to display a webpage that i would like to be loaded in a custom panel using WebView2 (as original WebBrowser is deprecated, and Cef has x64 problems).

However, the following code fails to execute properly and is throwing a com exception, when attempting to initialize the custom view:

class ChartView : Panel
    {
#nullable enable
        private string? chartViewFileAddr { get; set; }
#nullable disable

        private WebView2 chartWebView { get; set; }

        private async void ensureWeb2Init() => await chartWebView.EnsureCoreWebView2Async(null);
        private bool isInit = false;

        public ChartView(string? chartViewFileAddr) : base()
        {
            this.chartViewFileAddr= chartViewFileAddr ?? "";
            this.BorderStyle =  BorderStyle.Fixed3D;
            this.Size = new System.Drawing.Size(300, 500);
            this.Location = new Point(0, 320);
            this.Name = "ChartWebView";
            chartWebView= new WebView2();
            chartWebView.Size = this.Size;
            chartWebView.Location = new System.Drawing.Point(0, 0);
            if (isInit == false)
            {
                Task.Run(() => this.ensureWeb2Init()).Wait();
                isInit = true;
            }
            chartWebView.CoreWebView2.Navigate("google.com"); //base address for testing
            this.Controls.Add(chartWebView);
        }

    }

When it runs, it fails on calling ensureWeb2Init. If i attempt to run the task without the Wait function, it fails to run asynchronously, if i add the wait function, the async task throws the following:

System.Runtime.InteropServices.COMException: 'Cannot change thread mode after it is set. (0x80010106 (RPC_E_CHANGED_MODE))'

For whatever reason, i cannot get the WebView2 to initialize properly. Is there anything i am doing wrong here? thank you for your help.

CodePudding user response:

The exception means that the code must be run on a UI thread (specifically, the UI thread that created the control). This is common for UI COM components.

The code you posted is using Task.Run to execute the code on a thread pool thread. To run it on the UI thread instead, remove the Task.Run.

CodePudding user response:

The problem: As mentioned in the comments and the other answer, the issue is calling the method in a thread other than the UI thread. According to documentations:

Note that even though this method is asynchronous and returns a Task, it still must be called on the UI thread like most public functionality of most UI controls.

InvalidOperationException Thrown if this instance of CoreWebView2 is already disposed, or if the calling thread isn't the thread which created this object (usually the UI thread).

Solutions: You may want to consider either of the following solutions based on your requirements:

  • Set the Source property, instead of calling EnsureCoreWebView2Async

    You can set the WebView2.Source property, which will initialize the CoreWebView2 implicitly if it's not already initialized. Then you don't need to call EnsureCoreWebView2Async. For example:

     webView21.Source = new Uri("https://google.com");
    
  • Expose an async Initialize method, and await call it when you need

    If you need to call the EnsureCoreWebView2Async, you can expose an async method which does the initializations and await call it when you need it. For example:

    Define the following method for your control:

     public async Task InitializeControl(string url)
     {
         await webView21.EnsureCoreWebView2Async();
         webView21.CoreWebView2.Navigate("https://google.com");
     }
    

    Then use it like this, when you want to initialize it:

     private async void Form1_Load(object sender, EventArgs e)
     {
         await myControl.InitializeControl("https://www.google.com");
     }
    
  • Call EnsureCoreWebView2Async without await, then do rest of initializations in the event handler ofCoreWebView2InitializationCompleted event

    You can call the EnsureCoreWebView2Async without await, then you can just do the initialization in the CoreWebView2InitializationCompleted event handler. For example:

     webView21.CoreWebView2InitializationCompleted  = (obj, args) =>
     {
         webView21.CoreWebView2.Navigate("https://google.com");
     };
     webView21.EnsureCoreWebView2Async();
    

More information and references:

  • WebView2: A good overview of initialization of the control.
  • Source: An overview of the Source property.
  • EnsureCoreWebView2Async: Considerations when calling EnsureCoreWebView2Async method.
  • Related