Awesomium multithread

alexidsa's Avatar

alexidsa

01 Sep, 2011 03:05 PM via web

Here is the code I currently use to get the whole page screenshot:

public RenderBuffer GetRender(string url,
    SynchronizationContext synchronizationContext)
{
    WebView webView = null;
    synchronizationContext.Send(
        (o, s) => webView = WebCore.CreateWebView(1024, 768), null);

    synchronizationContext.Send(((o, s) => webView.LoadURL(url)), null);

    while (webView.IsLoadingPage)
        SleepAndUpdateCore(synchronizationContext);

    webView.RequestScrollData();
    webView.ScrollDataReceived += OnScrollDataReceived;

    while (!_finishedScrollDataReceived)
        SleepAndUpdateCore(synchronizationContext);

    webView.Resize(_scrollData.ContentWidth, _scrollData.ContentHeight);

    while (webView.IsResizing)
        SleepAndUpdateCore(synchronizationContext);

    return webView.Render();
}

private void OnScrollDataReceived(object sender, ScrollDataEventArgs e)
{
    _finishedScrollDataReceived = true;
    _scrollData = e.ScrollData;
}

private void SleepAndUpdateCore(SynchronizationContext synchronizationContext)
{
    Thread.Sleep(100);
    synchronizationContext.Send((object state) => WebCore.Update(), null);
}
This code should be executed by 10 threads. The problem is I have no idea which methods are thread safe and which are not. I had to synchronize LoadUrl method (in other case I got CorruptedMemoryException) which I guess will make my application almost one-thread (as url retrieval is the longest part). Is it by design?

I am using the Awesomium 1.6.2 and the last AwesomiumSharp.

  1. Support Staff 2 Posted by Perikles C. Stephanidis on 01 Sep, 2011 03:33 PM

    Perikles C. Stephanidis's Avatar

    Hello,

    Currently, types and their members in Awesomium and AwesomiumSharp are not thread-safe. It is also stated in the documentation (see Thread Safety section here but also in many other topics of interest). So this means that yes, all calls to any member in Awesomium and AwesomiumSharp, should be made from the thread that initiated the WebCore. It does not need to be a UI thread (although it's obvious that it has to be if you plan to render the produced buffer on UI and not just save it) as long as all calls keep coming from the same thread thoughout the lifetime of the WebCore and any web views created. Perhaps it was not made as clear as it should be and we apologize for that.

    Note that in AwesomiumSharp 1.6.2, if the WebCore is initiated from a UI thread, the necessary synchronization context is created internally and auto-update is enabled (see WebCore.IsAutoUpdateEnabled).

    There are plans bringing multi-threading back in later releases of Awesomium and AwesomiumSharp.

    Regards,
    Perikles

  2. 3 Posted by alexidsa on 01 Sep, 2011 05:50 PM

    alexidsa's Avatar

    But according to my example, some operations are thread-safe: as you can see, I don't wrap all WebCore/WebView calls to SyncronizationContext and it works... So I guess you mean you don't guarantee it is stable, yes?

    The main goal for me is to make url retrieval out of single (UI) thread. Do I achieve this goal by my code. webView.LoadUrl is asynchronous and is executed very quickly. And after that I'm waiting till page is downloaded in secondary thread. But I'm not sure in which process url is really retrieved.

    Sorry, I didn't get your point about IsAutoUpdateEnabled. How can I use it and what will it give to me?

  3. Support Staff 4 Posted by Perikles C. Stephanidis on 01 Sep, 2011 09:32 PM

    Perikles C. Stephanidis's Avatar

    Hello,

    • You are right. Not thread-safe means it is not safe to call any member from a background thread. This doesn't mean that every call will necessarily fail. Some calls may occasionally pass through, but you should not rely on this. The current status is that Awesomium is not thread-safe. This has to do with how Awesomium (the native code) is implemented. It is not an AwesomiumSharp (.NET wrapper) issue.

    • The event you should handle for your purpose is: WebView.LoadCompleted. But before this, let me explain auto-update:

      • Starting with AwesomiumSharp 1.6.2, if you initialize your WebCore from a UI thread, you no longer need to manually call WebCore.Update. The WebCore internally obtains a valid synchronization context from the UI thread and starts internal auto-update, that is performed in intervals indicated by the WebCore.AutoUpdatePeriod property (the default is every 20 milliseconds). Then, if what you want to do is actually draw the RenderBuffer on a surface, you simply monitor the WebView.IsDirtyChanged event. You can read the Remarks section in the documentation of WebCore.Update.
      • Please note that there can only be a single WebCore initialization per process. Unless you initialize the WebCore (WebCore.Initialize) somewhere else in your application before you call GetRender, then the core is automatically initialized (with default configuration settings), when you create your first view. Since in your case this happens within a SynchronizationContext.Send callback, then your WebCore is initialized in the UI thread and this means that you already get auto-update.
      • Also note that since you store the created WebView in a local variable, you should properly dispose this view before exiting the function. However, the appropriate method would be to actually store the WebView in a class-wide variable and reuse the same view for every URL. You do not need to create and destroy a view for every URL you load.

    In your case, it appears that you simply want to save a snapshot of the full page. Given my description above, you cannot achieve this by having multiple threads concurrently navigating to different URLs and getting images, neither can you achieve this completely synchronously, without blocking your UI thread. You need to rely on WebView events (to avoid blocking the UI) and only save one image at a time, possibly canceling a previous operation that has not completed yet, when a new URL request arrives, or, you can create a separate view for every request (as you already do). This however may be memory-consuming depending on how many images you try to save within a certain period.

    So, let me summarize all these by giving you a sample that should do what you try to do. In this sample I will demostrate both scenarios:

    1. Getting one RenderBuffer at a time (cancelling previous incomplete operation if needed).
    2. Creating a new WebView for every request (Memory-consuming if too many requests arrive too fast).

    For the sample I will use a WinForms Form:

    using System;
    using System.Windows.Forms;
    using AwesomiumSharp;
    using System.Collections.Generic;
    
    namespace WindowsFormsApplication1
    {
        public partial class Form1 : Form
        {
            private WebView webView;
    
            public Form1()
            {
                WebCore.Initialize( new WebCoreConfig() { SaveCacheAndCookies = false, LogLevel = LogLevel.None }, false );
    
                InitializeComponent();
    
                // Keeps references to web views created in SaveBufferParallel,
                // to avoid premature Garbage Collection. Important!
                parallelViews = new List<WebView>();
    
                // Web view used with serial requests.
                webView = WebCore.CreateWebView( 1024, 768 );
                webView.LoadCompleted += OnLoadCompleted;
                webView.ScrollDataReceived += OnScrollDataReceived;
            }
    
            private void button1_Click( object sender, EventArgs e )
            {
                this.SaveBufferSerial( "http://www.awesomium.com" );
            }
    
            private void button2_Click( object sender, EventArgs e )
            {
                this.SaveBufferParallel( "http://www.awesomium.com" );
            }
    
            #region Serial Model
            private void SaveBufferSerial( string url )
            {
                if ( !webView.IsEnabled )
                    return;
    
                // No need to check; we cancel the previous operation.
                webView.Stop();
                webView.LoadURL( url );
            }
    
            private void OnLoadCompleted( object sender, EventArgs e )
            {
                if ( !webView.IsEnabled )
                    return;
    
                webView.RequestScrollData();
            }
    
            private void OnScrollDataReceived( object sender, ScrollDataEventArgs e )
            {
                if ( !webView.IsEnabled )
                    return;
    
                webView.Resize( e.ScrollData.ContentWidth, e.ScrollData.ContentHeight );
    
                // This is the only place you may have to block for some milliseconds.
                // Currently, there is not WebView.Resize event, but we plan to implement one
                // in a later release.
                while ( webView.IsEnabled && webView.IsResizing )
                {
                    Application.DoEvents(); // This can help a bit.
                }
    
                // Avoid race condition.
                if ( webView.IsEnabled )
                {
                    RenderBuffer buffer = webView.Render();
    
                    // Do what you want with the buffer here.
                    buffer.SaveToPNG( "test.png" );
                }
            }
            #endregion
    
            #region Parallel Model
            // Keeps references to web views created in SaveBufferParallel,
            // to avoid premature Garbage Collection. Important!
            List<WebView> parallelViews;
    
            private void SaveBufferParallel( string url )
            {
                EventHandler loadCompleted = null;
                ScrollDataReceivedEventHandler scrollDataReceived = null;
    
                WebView view = WebCore.CreateWebView( 1024, 768 );
    
                parallelViews.Add( view );
    
                loadCompleted = ( s, e ) =>
                {
                    view.LoadCompleted -= loadCompleted;
    
                    if ( view.IsEnabled )
                        view.RequestScrollData();
                    else
                    {
                        view.Close();
                        parallelViews.Remove( view );
                    }
                };
    
                scrollDataReceived = ( s, e ) =>
                {
                    view.ScrollDataReceived -= scrollDataReceived;
    
                    if ( view.IsEnabled )
                    {
                        view.Resize( e.ScrollData.ContentWidth, e.ScrollData.ContentHeight );
    
                        // This is the only place you may have to block for some milliseconds.
                        // Currently, there is not WebView.Resize event, but we plan to implement one
                        // in a later release.
                        while ( view.IsEnabled && view.IsResizing )
                        {
                            Application.DoEvents(); // This can help a bit.
                        }
    
                        // Avoid a race condition.
                        if ( view.IsEnabled )
                        {
                            RenderBuffer buffer = view.Render();
    
                            // Do what you want with the buffer here.
                            buffer.SaveToPNG( "test.png" );
                        }
                    }
    
                    view.Close();
                    parallelViews.Remove( view );
                };
    
                view.LoadCompleted += loadCompleted;
                view.ScrollDataReceived += scrollDataReceived;
    
                view.LoadURL( url );
            }
            #endregion
    
            protected override void OnFormClosed( FormClosedEventArgs e )
            {
                if ( webView != null )
                    webView.Close();
    
                if ( parallelViews.Count > 0 )
                    parallelViews.ForEach( view => view.Close() );
    
                parallelViews.Clear();
    
                WebCore.Shutdown();
    
                base.OnFormClosed( e );
            }
        }
    }
    

    Regards,
    Perikles

  4. Perikles C. Stephanidis closed this discussion on 13 Sep, 2011 11:33 AM.

Comments are currently closed for this discussion. You can start a new one.