Skip to content

Commit d3e8938

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 d3e8938

File tree

4 files changed

+247
-54
lines changed

4 files changed

+247
-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" />
+172-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,108 @@ 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+
browser.ShowDevTools();
75+
76+
var response = await browser.EvaluateScriptAsync("document.querySelector('[name=q]').value = 'CefSharp Was Here!'");
77+
78+
//Give the browser a little time to render
79+
await Task.Delay(500);
80+
// Wait for the screenshot to be taken.
81+
var bitmap = await browser.ScreenshotAsync();
82+
83+
// File path to save our screenshot e.g. C:\Users\{username}\Desktop\CefSharp screenshot.png
84+
var screenshotPath = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.Desktop), "CefSharp screenshot.png");
85+
86+
Console.WriteLine();
87+
Console.WriteLine("Screenshot ready. Saving to {0}", screenshotPath);
88+
89+
// Save the Bitmap to the path.
90+
// The image type is auto-detected via the ".png" extension.
91+
bitmap.Save(screenshotPath);
92+
93+
// We no longer need the Bitmap.
94+
// Dispose it to avoid keeping the memory alive. Especially important in 32-bit applications.
95+
bitmap.Dispose();
96+
97+
Console.WriteLine("Screenshot saved. Launching your default image viewer...");
98+
99+
// Tell Windows to launch the saved image.
100+
Process.Start(new ProcessStartInfo(screenshotPath)
101+
{
102+
// UseShellExecute is false by default on .NET Core.
103+
UseShellExecute = true
104+
});
105+
106+
Console.WriteLine("Image viewer launched. Press any key to exit.");
107+
}
108+
109+
// Wait for user to press a key before exit
110+
Console.ReadKey();
111+
112+
// Clean up Chromium objects. You need to call this in your application otherwise
113+
// you will get a crash when closing.
114+
Cef.Shutdown();
115+
});
116+
117+
return 0;
118+
}
119+
120+
/// <summary>
121+
/// Synchronous demo using CefSharp.OffScreen
122+
/// Loads google.com, uses javascript to fill out the search box then takes a screenshot which is opened
123+
/// in the default image viewer.
124+
/// For a asynchronous demo see <see cref="Main(string[])"/> above.
125+
/// To use this demo simply delete the <see cref="Main(string[])"/> method and rename this method to Main.
126+
/// </summary>
127+
/// <param name="args">args</param>
128+
/// <returns>exit code</returns>
129+
public static int MainSync(string[] args)
130+
{
131+
#if ANYCPU
132+
//Only required for PlatformTarget of AnyCPU
133+
CefRuntime.SubscribeAnyCpuAssemblyResolver();
134+
#endif
135+
136+
const string testUrl = "https://www.google.com/";
137+
138+
Console.WriteLine("This example application will load {0}, take a screenshot, and save it to your desktop.", testUrl);
139+
Console.WriteLine("You may see Chromium debugging output, please wait...");
140+
Console.WriteLine();
141+
31142
var settings = new CefSettings()
32143
{
33144
//By default CefSharp will use an in-memory cache, you need to specify a Cache Folder to persist data
@@ -38,69 +149,76 @@ public static int Main(string[] args)
38149
Cef.Initialize(settings, performDependencyCheck: true, browserProcessHandler: null);
39150

40151
// Create the offscreen Chromium browser.
41-
browser = new ChromiumWebBrowser(testUrl);
152+
var browser = new ChromiumWebBrowser(testUrl);
42153

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;
154+
EventHandler<LoadingStateChangedEventArgs> handler = null;
46155

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

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();
165+
var scriptTask = browser.EvaluateScriptAsync("document.querySelector('[name=q]').value = 'CefSharp Was Here!'");
53166

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

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;
183+
Console.WriteLine();
184+
Console.WriteLine("Screenshot ready. Saving to {0}", screenshotPath);
66185

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

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");
190+
// We no longer need the Bitmap.
191+
// Dispose it to avoid keeping the memory alive. Especially important in 32-bit applications.
192+
task.Result.Dispose();
193+
194+
Console.WriteLine("Screenshot saved. Launching your default image viewer...");
195+
196+
// Tell Windows to launch the saved image.
197+
Process.Start(new ProcessStartInfo(screenshotPath)
198+
{
199+
// UseShellExecute is false by default on .NET Core.
200+
UseShellExecute = true
201+
});
79202

80-
Console.WriteLine();
81-
Console.WriteLine("Screenshot ready. Saving to {0}", screenshotPath);
203+
Console.WriteLine("Image viewer launched. Press any key to exit.");
204+
}, TaskScheduler.Default);
205+
});
206+
}
207+
};
82208

83-
// Save the Bitmap to the path.
84-
// The image type is auto-detected via the ".png" extension.
85-
task.Result.Save(screenshotPath);
209+
// An event that is fired when the first page is finished loading.
210+
// This returns to us from another thread.
211+
browser.LoadingStateChanged += handler;
86212

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();
213+
// We have to wait for something, otherwise the process will exit too soon.
214+
Console.ReadKey();
90215

91-
Console.WriteLine("Screenshot saved. Launching your default image viewer...");
216+
// Clean up Chromium objects. You need to call this in your application otherwise
217+
// you will get a crash when closing.
218+
//The ChromiumWebBrowser instance will be disposed
219+
Cef.Shutdown();
92220

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-
}
221+
return 0;
104222
}
105223
}
106224
}
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)