Search code examples
c#async-awaitchromium-embeddedcefsharp

Can't make CefSharp load synchronous


I have an off screen ChromiumWebBrowser that I use to run some JS on a page and get back the results.

The Load method is synchronous, but I can't run JS code until the FrameLoadEnd event is raised, which means that or my purposes Load is an asynchronous method that ends in the FrameLoadEnd event.

To make my code clearer, I tried to create an extension method that will allow me to wait for the page to load using await instead of registering to the event. But when I use this method with TaskCompletionSource the javascript that is supposed to run after the page loads isn't loaded, but when using an AutoResetEvent and waiting on it the code does work.

This code doesn't work:

public static Task LoadPageAsync(this IWebBrowser browser, string address)
{
    TaskCompletionSource<bool> tcs = new TaskCompletionSource<bool>();
    browser.FrameLoadEnd += (sender, args) =>
    {
        if (args.IsMainFrame)
        {
            tcs.TrySetResult(true);
        }
    };
    browser.Load(address);
    return tcs.Task;
}

And this does:

public static AutoResetEvent LoadPageAsync(this IWebBrowser browser, string address)
{
    AutoResetEvent are = new AutoResetEvent(false);
    browser.FrameLoadEnd += (sender, args) =>
    {
        if (args.IsMainFrame)
        {
            are.Set();
        }
    };
    browser.Load(address);
    return are;
}

This is the calling code:

await _browser.LoadPageAsync("Some web address");
LoadScripts();

DoJsThing();
GetJsData();

They do the same thing but I feel that the intent of the function is much clearer when returning a task than when returning an AutoResetEvent.

Why can't I use TaskCompletionSource to indicate I finished? Am I doing something wrong?


Solution

  • public static Task LoadPageAsync(IWebBrowser browser, string address = null)
    {
        var tcs = new TaskCompletionSource<bool>(TaskCreationOptions.RunContinuationsAsynchronously);
    
        EventHandler<LoadingStateChangedEventArgs> handler = null;
        handler = (sender, args) =>
        {
            //Wait for while page to finish loading not just the first frame
            if (!args.IsLoading)
            {
                browser.LoadingStateChanged -= handler;
                //Important that the continuation runs async using TaskCreationOptions.RunContinuationsAsynchronously
                tcs.TrySetResult(true);
            }
        };
    
        browser.LoadingStateChanged += handler;
    
        if (!string.IsNullOrEmpty(address))
        {
            browser.Load(address);
        }
        return tcs.Task;
    }
    

    Note I used LoadingStateChanged as it's a better indicator of when the whole page has finished loading.