6

.NET, WinForms.

The calls are triggered from the UI thread (buttons - clicks). The returns from ExecuteScriptAsync should continue to be processed synchronously, i.e. they should be synchronized again with the call context. I fail here.

I tried for example:

private void buttonTest1_Click(object sender, EventArgs e) {
        MessageBox.Show(GetMathResult());
    }

    String GetMathResult() {
        // a) Application freezes
        //var result = webView.ExecuteScriptAsync("Math.sin(Math.PI/2)").GetAwaiter().GetResult();
        //return result;

        // b) return null
        //String result = null;
        //Task task = new Task(async () => { result = await webView.ExecuteScriptAsync("Math.sin(Math.PI/2)"); }); 
        //task.RunSynchronously();
        //return result;

        // c) Excepion: // InvalidCastException: Das COM-Objekt des Typs "System.__ComObject" kann nicht in den Schnittstellentyp "Microsoft.Web.WebView2.Core.Raw.ICoreWebView2Controller" umgewandelt werden. Dieser Vorgang konnte nicht durchgeführt werden, da der QueryInterface-Aufruf an die COM - Komponente für die Schnittstelle mit der IID "{4D00C0D1-9434-4EB6-8078-8697A560334F}" aufgrund des folgenden Fehlers nicht durchgeführt werden konnte: Schnittstelle nicht unterstützt(Ausnahme von HRESULT: 0x80004002(E_NOINTERFACE)).
        //String result = Task.Run(() => GetMathResultTask()).Result;
        //return result;
    }

    Task<String> GetMathResultTask() {
        return webView.ExecuteScriptAsync("Math.sin(Math.PI/2)");
    }

And that doesn't work either (see error):

private void buttonTest3_Click(object sender, EventArgs e) {
        MessageBox.Show(Y());
    }

    String Y() {
        String result = null;
        var autoResetEvent = new AutoResetEvent(false);

        Task.Run(async () =>
        {
            try {
                result = await webView.ExecuteScriptAsync("Math.sin(Math.PI/2)");
            }
            catch (Exception exc) {
                // !!! {"Das COM-Objekt des Typs \"System.__ComObject\" kann nicht in den Schnittstellentyp \"Microsoft.Web.WebView2.Core.Raw.ICoreWebView2Controller\" umgewandelt werden. Dieser Vorgang konnte nicht durchgeführt werden, da der QueryInterface-Aufruf an die COM-Komponente für die Schnittstelle mit der IID \"{4D00C0D1-9434-4EB6-8078-8697A560334F}\" aufgrund des folgenden Fehlers nicht durchgeführt werden konnte: Schnittstelle nicht unterstützt (Ausnahme von HRESULT: 0x80004002 (E_NOINTERFACE))."}
                Console.WriteLine(exc.ToString()); 
            }
            finally {
                autoResetEvent.Set();
            }
        });
        autoResetEvent.WaitOne();

        return result;
    }

I am bidding for a code sample.

Sergej Loos
  • 292
  • 1
  • 2
  • 10
  • 1
    Like any other asycn method, use await. – Reza Aghaei Dec 16 '20 at 18:39
  • I think I tried everything as suggested in the link below and it doesn't work: https://stackoverflow.com/questions/5095183/how-would-i-run-an-async-taskt-method-synchronously – Sergej Loos Dec 17 '20 at 07:58
  • Update the question and add what you tried and what didn't work. `it doesn't work` isn't a problem description. You don't need complicated tricks to await an asynchronous method and get any results. `var result=await whatever.ExecuteScriptAsync(....);` – Panagiotis Kanavos Dec 17 '20 at 08:20
  • After `await` finishes you'll be back in the UI thread. So what does ` I fail here.` mean? Did you try executing the call inside `Task.Run` perhaps, or from another thread? – Panagiotis Kanavos Dec 17 '20 at 08:22
  • Don't forget, I cannot return the result from the method after the "var result = await whatever.ExecuteScriptAsync (....)". – Sergej Loos Dec 17 '20 at 08:42
  • `private async void button1_Click(object sender, EventArgs e) { MessageBox.Show(await GetMathResultTask()); }` – Reza Aghaei Dec 17 '20 at 08:45
  • @ Reza Aghaei: I need a method with a return value – Sergej Loos Dec 17 '20 at 08:49

5 Answers5

9

To get the result from ExecuteScriptAsync use await operator, like this:

private async void Form1_Load(object sender, EventArgs e)
{
    await this.webView21.EnsureCoreWebView2Async();
}
private async void button1_Click(object sender, EventArgs e)
{
    var result = await webView21.ExecuteScriptAsync("Math.sin(Math.PI/2)");
    MessageBox.Show(result);
}

Note: For those who want to use WebView2, you need to have WebView2 Runtime and Microsoft Edge Chromium installed on your machine. You also need to install WebView2 NuGet package in your project.

Reza Aghaei
  • 120,393
  • 18
  • 203
  • 398
  • Thanks! I can do that. Where I fail is creating a return method. Based on their example: async String GetMathResult() { var result = await webView.ExecuteScriptAsync("Math.sin(Math.PI/2)"); return result; } – Sergej Loos Dec 17 '20 at 07:50
  • @SergejLoos what you typed works, once you change it to `async Task`. What's the problem? What did you actually try? – Panagiotis Kanavos Dec 17 '20 at 08:23
  • I edited my question. Hopefully it has become more understandable – Sergej Loos Dec 17 '20 at 08:44
  • @SergejLoos I was searching for something and I stumbled on this post, which doesn't have an accepted answer. JFYI, you can accept your own answer as well; however, I believe this answer is a much better approach comparing to the other solutions (including your own answer) :) - Anyhow, marking an answer as accepted, highlights it for future readers as *probably* a reliable solution. – Reza Aghaei Jan 04 '23 at 11:44
4

I will probably have to use it that way. This is the best solution I could find:

private void buttonTest3_Click(object sender, EventArgs e) {
        GetMathResult_v3((x) => {
            MessageBox.Show(x);
            // .. 
        });
    }

    void GetMathResult_v3(Action<String> callbackAction) {

        var task = webView.ExecuteScriptAsync("Math.sin(Math.PI/2)");

        task.ContinueWith(
            (x) => {
                String mathResult = x.Result;
                callbackAction(mathResult);
            }
            , TaskScheduler.FromCurrentSynchronizationContext()
        );

    }
Sergej Loos
  • 292
  • 1
  • 2
  • 10
2

For Anyone working with WebView2 under WinForms and looking for Safe, Simple and Anti-Blocking Solution these lines of codes may seem logical:

public void WaitAsyncTask(int waitSecond, System.Runtime.CompilerServices.TaskAwaiter activeTask)
{
    double secondDiffer = (DateTime.Now - WaitStartTime).TotalSeconds;
    while (secondDiffer < waitSecond)
    {
        secondDiffer = (DateTime.Now - WaitStartTime).TotalSeconds;
        Application.DoEvents();
        if (activeTask.IsCompleted)
            break;
    }
}

WaitStartTime is user defined and assigned before Async process start.

To call this method from main method:

public async Task ExecJavaScript(string userScript)
{
    MacroJava = "";
    var tr = await ((Microsoft.Web.WebView2.WinForms.WebView2)wbSelectedTech).CoreWebView2.ExecuteScriptAsync(userScript);
    MacroJava = System.Text.Json.JsonSerializer.Deserialize<dynamic>(tr);
    MacroJava = MacroJava is null ? "" : MacroJava.ToString();
}

Now call these helper methods from this method:

public string GetelementValue(string elmId)
{
   var aw = ExecJavaScript("document.getElementById('" + elmId + "').innerHTML").GetAwaiter();
   TimeWaitStart = DateTime.Now;
   WaitAsyncTask(2, aw);//for 2 seconds maximum 
   return MacroJava.ToString();
}
Uwe Keim
  • 39,551
  • 56
  • 175
  • 291
2

I used my own solution which worked for me, but I don't know if it can cause any problems in the future (if someone knows, please add comment). It waits 2 seconds for script to execute before jumping out:

int max_count = 200;
var task = webView2.ExecuteScriptAsync(script);
while (!task.IsCompleted && --max_count > 0)
{
    Application.DoEvents();
    System.Threading.Thread.Sleep(10);
}
if (max_count > 0)
{
    result = JsonConvert.DeserializeObject(task.Result)?.ToString() ?? "";
}
else
{
    // Timeout
}
Nikola
  • 125
  • 1
  • 8
-2
webView.ExecuteScriptAsync("Math.sin(Math.PI/2)").Result;
Koxo
  • 507
  • 4
  • 10