Skip to content

Commit a5d2bbe

Browse files
committed
OffScreen - Add async example
- Old example is still there for reference, just not used by default - Add AsyncContext/SingleThreadSynchronizationContext to ensure async calls continue on main thread.
1 parent 3cc3e64 commit a5d2bbe

File tree

4 files changed

+245
-54
lines changed

4 files changed

+245
-54
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
/// https://devblogs.microsoft.com/pfxteam/await-synchronizationcontext-and-console-apps/
2+
3+
using System;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
7+
namespace CefSharp.MinimalExample.OffScreen
8+
{
9+
public static class AsyncContext
10+
{
11+
public static void Run(Func<Task> func)
12+
{
13+
var prevCtx = SynchronizationContext.Current;
14+
15+
try
16+
{
17+
var syncCtx = new SingleThreadSynchronizationContext();
18+
19+
SynchronizationContext.SetSynchronizationContext(syncCtx);
20+
21+
var t = func();
22+
23+
t.ContinueWith(delegate
24+
{
25+
syncCtx.Complete();
26+
}, TaskScheduler.Default);
27+
28+
syncCtx.RunOnCurrentThread();
29+
30+
t.GetAwaiter().GetResult();
31+
}
32+
finally
33+
{
34+
SynchronizationContext.SetSynchronizationContext(prevCtx);
35+
}
36+
}
37+
}
38+
}

CefSharp.MinimalExample.OffScreen/CefSharp.MinimalExample.OffScreen.csproj

+5
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,9 @@
8484
<PropertyGroup>
8585
<ApplicationManifest>app.manifest</ApplicationManifest>
8686
</PropertyGroup>
87+
<PropertyGroup>
88+
<StartupObject>CefSharp.MinimalExample.OffScreen.Program</StartupObject>
89+
</PropertyGroup>
8790
<ItemGroup>
8891
<Reference Include="CefSharp, Version=95.7.141.0, Culture=neutral, PublicKeyToken=40c4b6fc221f4138">
8992
<HintPath>..\packages\CefSharp.Common.95.7.141\lib\net452\CefSharp.dll</HintPath>
@@ -103,8 +106,10 @@
103106
<Reference Include="Microsoft.CSharp" />
104107
</ItemGroup>
105108
<ItemGroup>
109+
<Compile Include="AsyncContext.cs" />
106110
<Compile Include="Program.cs" />
107111
<Compile Include="Properties\AssemblyInfo.cs" />
112+
<Compile Include="SingleThreadSynchronizationContext.cs" />
108113
</ItemGroup>
109114
<ItemGroup>
110115
<None Include="app.config" />
+170-54
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Copyright © 2010-2015 The CefSharp Authors. All rights reserved.
1+
// Copyright © 2010-2021 The CefSharp Authors. All rights reserved.
22
//
33
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
44

@@ -11,10 +11,19 @@
1111

1212
namespace CefSharp.MinimalExample.OffScreen
1313
{
14+
/// <summary>
15+
/// CefSharp.OffScreen Minimal Example
16+
/// </summary>
1417
public class Program
1518
{
16-
private static ChromiumWebBrowser browser;
17-
19+
/// <summary>
20+
/// Asynchronous demo using CefSharp.OffScreen
21+
/// Loads google.com, uses javascript to fill out the search box then takes a screenshot which is opened
22+
/// in the default image viewer.
23+
/// For a synchronous demo see <see cref="MainSync(string[])"/> below.
24+
/// </summary>
25+
/// <param name="args">args</param>
26+
/// <returns>exit code</returns>
1827
public static int Main(string[] args)
1928
{
2029
#if ANYCPU
@@ -28,6 +37,106 @@ public static int Main(string[] args)
2837
Console.WriteLine("You may see Chromium debugging output, please wait...");
2938
Console.WriteLine();
3039

40+
//Console apps don't have a SynchronizationContext, so to ensure our await calls continue on the main thread we use a super simple implementation from
41+
//https://devblogs.microsoft.com/pfxteam/await-synchronizationcontext-and-console-apps/
42+
//Continuations will happen on the main thread. Cef.Initialize/Cef.Shutdown must be called on the same Thread.
43+
//The Nito.AsyncEx.Context Nuget package has a more advanced implementation
44+
//should you wish to use a pre-build implementation.
45+
//https://github.com/StephenCleary/AsyncEx/blob/8a73d0467d40ca41f9f9cf827c7a35702243abb8/doc/AsyncContext.md#console-example-using-asynccontext
46+
//NOTE: This is only required if you use await
47+
48+
AsyncContext.Run(async delegate
49+
{
50+
var settings = new CefSettings()
51+
{
52+
//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data
53+
CachePath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.LocalApplicationData), "CefSharp\\Cache")
54+
};
55+
56+
//Perform dependency check to make sure all relevant resources are in our output directory.
57+
var success = await Cef.InitializeAsync(settings, performDependencyCheck: true, browserProcessHandler: null);
58+
59+
if (!success)
60+
{
61+
throw new Exception("Unable to initialize CEF, check the log file.");
62+
}
63+
64+
// Create the CefSharp.OffScreen.ChromiumWebBrowser instance
65+
using (var browser = new ChromiumWebBrowser(testUrl))
66+
{
67+
var initialLoadResponse = await browser.WaitForInitialLoadAsync();
68+
69+
if (!initialLoadResponse.Success)
70+
{
71+
throw new Exception(string.Format("Page load failed with ErrorCode:{0}, HttpStatusCode:{1}", initialLoadResponse.ErrorCode, initialLoadResponse.HttpStatusCode));
72+
}
73+
74+
var response = await browser.EvaluateScriptAsync("document.querySelector('[name=q]').value = 'CefSharp Was Here!'");
75+
76+
//Give the browser a little time to render
77+
await Task.Delay(500);
78+
// Wait for the screenshot to be taken.
79+
var bitmap = await browser.ScreenshotAsync();
80+
81+
// File path to save our screenshot e.g. C:\Users\{username}\Desktop\CefSharp screenshot.png
82+
var screenshotPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "CefSharp screenshot.png");
83+
84+
Console.WriteLine();
85+
Console.WriteLine("Screenshot ready. Saving to {0}", screenshotPath);
86+
87+
// Save the Bitmap to the path.
88+
// The image type is auto-detected via the ".png" extension.
89+
bitmap.Save(screenshotPath);
90+
91+
// We no longer need the Bitmap.
92+
// Dispose it to avoid keeping the memory alive. Especially important in 32-bit applications.
93+
bitmap.Dispose();
94+
95+
Console.WriteLine("Screenshot saved. Launching your default image viewer...");
96+
97+
// Tell Windows to launch the saved image.
98+
Process.Start(new ProcessStartInfo(screenshotPath)
99+
{
100+
// UseShellExecute is false by default on .NET Core.
101+
UseShellExecute = true
102+
});
103+
104+
Console.WriteLine("Image viewer launched. Press any key to exit.");
105+
}
106+
107+
// Wait for user to press a key before exit
108+
Console.ReadKey();
109+
110+
// Clean up Chromium objects. You need to call this in your application otherwise
111+
// you will get a crash when closing.
112+
Cef.Shutdown();
113+
});
114+
115+
return 0;
116+
}
117+
118+
/// <summary>
119+
/// Synchronous demo using CefSharp.OffScreen
120+
/// Loads google.com, uses javascript to fill out the search box then takes a screenshot which is opened
121+
/// in the default image viewer.
122+
/// For a asynchronous demo see <see cref="Main(string[])"/> above.
123+
/// To use this demo simply delete the <see cref="Main(string[])"/> method and rename this method to Main.
124+
/// </summary>
125+
/// <param name="args">args</param>
126+
/// <returns>exit code</returns>
127+
public static int MainSync(string[] args)
128+
{
129+
#if ANYCPU
130+
//Only required for PlatformTarget of AnyCPU
131+
CefRuntime.SubscribeAnyCpuAssemblyResolver();
132+
#endif
133+
134+
const string testUrl = "https://www.google.com/";
135+
136+
Console.WriteLine("This example application will load {0}, take a screenshot, and save it to your desktop.", testUrl);
137+
Console.WriteLine("You may see Chromium debugging output, please wait...");
138+
Console.WriteLine();
139+
31140
var settings = new CefSettings()
32141
{
33142
//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data
@@ -38,69 +147,76 @@ public static int Main(string[] args)
38147
Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);
39148

40149
// Create the offscreen Chromium browser.
41-
browser = new ChromiumWebBrowser(testUrl);
150+
var browser = new ChromiumWebBrowser(testUrl);
42151

43-
// An event that is fired when the first page is finished loading.
44-
// This returns to us from another thread.
45-
browser.LoadingStateChanged += BrowserLoadingStateChanged;
152+
EventHandler<LoadingStateChangedEventArgs> handler = null;
46153

47-
// We have to wait for something, otherwise the process will exit too soon.
48-
Console.ReadKey();
154+
handler = (s, e) =>
155+
{
156+
// Check to see if loading is complete - this event is called twice, one when loading starts
157+
// second time when it's finished
158+
if (!e.IsLoading)
159+
{
160+
// Remove the load event handler, because we only want one snapshot of the page.
161+
browser.LoadingStateChanged -= handler;
49162

50-
// Clean up Chromium objects. You need to call this in your application otherwise
51-
// you will get a crash when closing.
52-
Cef.Shutdown();
163+
var scriptTask = browser.EvaluateScriptAsync("document.querySelector('[name=q]').value = 'CefSharp Was Here!'");
53164

54-
return 0;
55-
}
165+
scriptTask.ContinueWith(t =>
166+
{
167+
if(!t.Result.Success)
168+
{
169+
throw new Exception("EvaluateScriptAsync failed:" + t.Result.Message);
170+
}
171+
172+
//Give the browser a little time to render
173+
Thread.Sleep(500);
174+
// Wait for the screenshot to be taken.
175+
var task = browser.ScreenshotAsync();
176+
task.ContinueWith(x =>
177+
{
178+
// File path to save our screenshot e.g. C:\Users\{username}\Desktop\CefSharp screenshot.png
179+
var screenshotPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "CefSharp screenshot.png");
56180

57-
private static void BrowserLoadingStateChanged(object sender, LoadingStateChangedEventArgs e)
58-
{
59-
// Check to see if loading is complete - this event is called twice, one when loading starts
60-
// second time when it's finished
61-
// (rather than an iframe within the main frame).
62-
if (!e.IsLoading)
63-
{
64-
// Remove the load event handler, because we only want one snapshot of the initial page.
65-
browser.LoadingStateChanged -= BrowserLoadingStateChanged;
181+
Console.WriteLine();
182+
Console.WriteLine("Screenshot ready. Saving to {0}", screenshotPath);
66183

67-
var scriptTask = browser.EvaluateScriptAsync("document.querySelector('[name=q]').value = 'CefSharp Was Here!'");
184+
// Save the Bitmap to the path.
185+
// The image type is auto-detected via the ".png" extension.
186+
task.Result.Save(screenshotPath);
68187

69-
scriptTask.ContinueWith(t =>
70-
{
71-
//Give the browser a little time to render
72-
Thread.Sleep(500);
73-
// Wait for the screenshot to be taken.
74-
var task = browser.ScreenshotAsync();
75-
task.ContinueWith(x =>
76-
{
77-
// Make a file to save it to (e.g. C:\Users\jan\Desktop\CefSharp screenshot.png)
78-
var screenshotPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "CefSharp screenshot.png");
188+
// We no longer need the Bitmap.
189+
// Dispose it to avoid keeping the memory alive. Especially important in 32-bit applications.
190+
task.Result.Dispose();
79191

80-
Console.WriteLine();
81-
Console.WriteLine("Screenshot ready. Saving to {0}", screenshotPath);
192+
Console.WriteLine("Screenshot saved. Launching your default image viewer...");
82193

83-
// Save the Bitmap to the path.
84-
// The image type is auto-detected via the ".png" extension.
85-
task.Result.Save(screenshotPath);
194+
// Tell Windows to launch the saved image.
195+
Process.Start(new ProcessStartInfo(screenshotPath)
196+
{
197+
// UseShellExecute is false by default on .NET Core.
198+
UseShellExecute = true
199+
});
86200

87-
// We no longer need the Bitmap.
88-
// Dispose it to avoid keeping the memory alive. Especially important in 32-bit applications.
89-
task.Result.Dispose();
201+
Console.WriteLine("Image viewer launched. Press any key to exit.");
202+
}, TaskScheduler.Default);
203+
});
204+
}
205+
};
90206

91-
Console.WriteLine("Screenshot saved. Launching your default image viewer...");
207+
// An event that is fired when the first page is finished loading.
208+
// This returns to us from another thread.
209+
browser.LoadingStateChanged += handler;
92210

93-
// Tell Windows to launch the saved image.
94-
Process.Start(new ProcessStartInfo(screenshotPath)
95-
{
96-
// UseShellExecute is false by default on .NET Core.
97-
UseShellExecute = true
98-
});
99-
100-
Console.WriteLine("Image viewer launched. Press any key to exit.");
101-
}, TaskScheduler.Default);
102-
});
103-
}
211+
// We have to wait for something, otherwise the process will exit too soon.
212+
Console.ReadKey();
213+
214+
// Clean up Chromium objects. You need to call this in your application otherwise
215+
// you will get a crash when closing.
216+
//The ChromiumWebBrowser instance will be disposed
217+
Cef.Shutdown();
218+
219+
return 0;
104220
}
105221
}
106222
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
/// https://devblogs.microsoft.com/pfxteam/await-synchronizationcontext-and-console-apps/
2+
3+
using System.Collections.Concurrent;
4+
using System.Collections.Generic;
5+
using System.Threading;
6+
7+
namespace CefSharp.MinimalExample.OffScreen
8+
{
9+
public sealed class SingleThreadSynchronizationContext : SynchronizationContext
10+
{
11+
private readonly BlockingCollection<KeyValuePair<SendOrPostCallback, object>> queue =
12+
new BlockingCollection<KeyValuePair<SendOrPostCallback, object>>();
13+
14+
public override void Post(SendOrPostCallback d, object state)
15+
{
16+
queue.Add(new KeyValuePair<SendOrPostCallback, object>(d, state));
17+
}
18+
19+
public void RunOnCurrentThread()
20+
{
21+
while (queue.TryTake(out var workItem, Timeout.Infinite))
22+
{
23+
workItem.Key(workItem.Value);
24+
}
25+
}
26+
27+
public void Complete()
28+
{
29+
queue.CompleteAdding();
30+
}
31+
}
32+
}

0 commit comments

Comments
 (0)