Skip to content

Commit

Permalink
Async JavascriptBinding - Add support for returning Task
Browse files Browse the repository at this point in the history
Rewrite MethodRunnerQueue to use Func instead of task as it simplified debugging, for the old code path rather
than running the Tasks sync we just execute the fun
For the new code path the Func is executed, if the Result is a generic Task we wait for it's completion
If the task is not generic then we return immediately

This is only enabled when CefSharpSettings.ConcurrentTaskExecution = true;

Additional tests will be required before this is production ready
  • Loading branch information
amaitland committed May 7, 2019
1 parent 049f576 commit a9f20ac
Show file tree
Hide file tree
Showing 6 changed files with 114 additions and 42 deletions.
2 changes: 1 addition & 1 deletion CefSharp.Core/ManagedCefBrowserAdapter.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ namespace CefSharp
_webBrowserInternal = webBrowserInternal;
_javaScriptObjectRepository = gcnew CefSharp::Internals::JavascriptObjectRepository();
_javascriptCallbackFactory = gcnew CefSharp::Internals::JavascriptCallbackFactory(_clientAdapter->GetPendingTaskRepository());
_methodRunnerQueue = gcnew CefSharp::Internals::MethodRunnerQueue(_javaScriptObjectRepository);
_methodRunnerQueue = gcnew CefSharp::Internals::MethodRunnerQueue(_javaScriptObjectRepository, CefSharpSettings::ConcurrentTaskExecution);
_methodRunnerQueue->MethodInvocationComplete += gcnew EventHandler<MethodInvocationCompleteArgs^>(this, &ManagedCefBrowserAdapter::MethodInvocationComplete);
_methodRunnerQueue->Start();
}
Expand Down
2 changes: 1 addition & 1 deletion CefSharp.Example/CefExample.cs
Original file line number Diff line number Diff line change
Expand Up @@ -188,7 +188,7 @@ public static void Init(AbstractCefSettings settings, IBrowserProcessHandler bro
CefSharpSettings.FocusedNodeChangedEnabled = true;

//Experimental option where bound async methods are queued on TaskScheduler.Default.
//CefSharpSettings.ConcurrentTaskExecution = true;
CefSharpSettings.ConcurrentTaskExecution = true;

//Legacy Binding Behaviour doesn't work for cross-site navigation (navigating to a different domain)
//See issue https://github.com/cefsharp/CefSharp/issues/1203 for details
Expand Down
13 changes: 13 additions & 0 deletions CefSharp.Example/JavascriptBinding/AsyncBoundObject.cs
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
using System.Diagnostics;
using System.Text;
using System.Threading;
using System.Threading.Tasks;

namespace CefSharp.Example.JavascriptBinding
{
Expand Down Expand Up @@ -147,5 +148,17 @@ public List<List<string>> MethodReturnsListOfLists()
{"data", MethodReturnsDictionary2()}
};
}

public Task<string> ReturnTaskStringAsync()
{
return Task.FromResult(nameof(ReturnTaskStringAsync));
}

public async void VoidReturnAsync()
{
await Task.Delay(1000);

Debug.WriteLine("Delayed 1 second.");
}
}
}
55 changes: 29 additions & 26 deletions CefSharp.Example/Resources/BindingTestSingle.html
Original file line number Diff line number Diff line change
@@ -1,52 +1,55 @@
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<html>
<head>
<title>Binding Test</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.4.1.css">
</head>
<body>
<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.4.1.js"></script>

<script type="text/javascript">
<head>
<title>Binding Test</title>
<link rel="stylesheet" href="https://code.jquery.com/qunit/qunit-2.4.1.css">
</head>
<body>
<div>
These tests require CefSharpSettings.ConcurrentTaskExecution = true;
Which by default is set to false
</div>

<div id="qunit"></div>
<div id="qunit-fixture"></div>
<script src="https://code.jquery.com/qunit/qunit-2.4.1.js"></script>

<script type="text/javascript">
(async () =>
{
await CefSharp.BindObjectAsync("boundAsync", "bound");
await CefSharp.BindObjectAsync("boundAsync");

QUnit.test( "Struct Test:", function( assert )
QUnit.test("ReturnTaskStringAsync:", function (assert)
{
var asyncCallback = assert.async();

//Returns a Struct
boundAsync.returnObject('CefSharp Struct Test').then(function (actualResult)
boundAsync.returnTaskStringAsync().then(function (actualResult)
{
const expectedResult = 'CefSharp Struct Test';
const expectedResult = 'ReturnTaskStringAsync';

assert.equal(expectedResult, actualResult.Value, "Return class " + expectedResult);
assert.equal(expectedResult, actualResult, "Return string " + expectedResult);

asyncCallback();
});
});
});

QUnit.test( "Class Test:", function( assert )
QUnit.test("VoidReturnAsync Test:", function (assert)
{
var asyncCallback = assert.async();

//Returns a class
boundAsync.returnClass('CefSharp Class Test').then(function (actualResult)
boundAsync.voidReturnAsync().then(function (actualResult)
{
const expectedResult = 'CefSharp Class Test';
const expectedResult = null;

assert.equal(expectedResult, actualResult.Value, "Return class " + expectedResult);
assert.equal(expectedResult, actualResult, "Return null");

asyncCallback();
});
});
});


})();
</script>
</script>

</body>
</body>
</html>
3 changes: 2 additions & 1 deletion CefSharp.Wpf.Example/MainWindow.xaml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Window x:Class="CefSharp.Wpf.Example.MainWindow"
<Window x:Class="CefSharp.Wpf.Example.MainWindow"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:controls="clr-namespace:CefSharp.Wpf.Example.Controls"
Expand Down Expand Up @@ -26,6 +26,7 @@
</MenuItem>
<MenuItem Header="_Tests">
<MenuItem Header="_Binding Test" Command="controls:CefSharpCommands.OpenTabCommand" CommandParameter="{Binding Source={x:Static ex:CefExample.BindingTestUrl}}"/>
<MenuItem Header="_Binding Test Single" Command="controls:CefSharpCommands.OpenTabCommand" CommandParameter="{Binding Source={x:Static ex:CefExample.BindingTestSingleUrl}}"/>
<MenuItem Header="_LegacyBinding Test" Command="controls:CefSharpCommands.OpenTabCommand" CommandParameter="{Binding Source={x:Static ex:CefExample.LegacyBindingTestUrl}}"/>
<MenuItem Header="_List Plugins" Command="controls:CefSharpCommands.OpenTabCommand" CommandParameter="{Binding Source={x:Static ex:CefExample.PluginsTestUrl}}"/>
<MenuItem Header="_Tooltip Test" Command="controls:CefSharpCommands.OpenTabCommand" CommandParameter="{Binding Source={x:Static ex:CefExample.TooltipTestUrl}}"/>
Expand Down
81 changes: 68 additions & 13 deletions CefSharp/Internals/MethodRunnerQueue.cs
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,20 @@ namespace CefSharp.Internals
{
public sealed class MethodRunnerQueue
{
private readonly bool concurrentTaskExecution;
private readonly JavascriptObjectRepository repository;
private readonly AutoResetEvent stopped = new AutoResetEvent(false);
private readonly BlockingCollection<Task<MethodInvocationResult>> queue = new BlockingCollection<Task<MethodInvocationResult>>();
private readonly BlockingCollection<Func<MethodInvocationResult>> queue = new BlockingCollection<Func<MethodInvocationResult>>();
private readonly object lockObject = new object();
private volatile CancellationTokenSource cancellationTokenSource;
private volatile bool running;

public event EventHandler<MethodInvocationCompleteArgs> MethodInvocationComplete;

public MethodRunnerQueue(JavascriptObjectRepository repository)
public MethodRunnerQueue(JavascriptObjectRepository repository, bool concurrentTaskExecution)
{
this.repository = repository;
this.concurrentTaskExecution = concurrentTaskExecution;
}

public void Start()
Expand Down Expand Up @@ -59,36 +61,89 @@ public void Stop()

public void Enqueue(MethodInvocation methodInvocation)
{
var task = new Task<MethodInvocationResult>(() => ExecuteMethodInvocation(methodInvocation));
queue.Add(task);
queue.Add(() => ExecuteMethodInvocation(methodInvocation));
}

private void ConsumeTasks()
{
try
{

if (CefSharpSettings.ConcurrentTaskExecution)
if (concurrentTaskExecution)
{
//New experimental behaviour that Starts the Tasks on TaskScheduler.Default
while (!cancellationTokenSource.IsCancellationRequested)
{
var task = queue.Take(cancellationTokenSource.Token);
task.ContinueWith((t) =>
var func = queue.Take(cancellationTokenSource.Token);

var task = new Task(() =>
{
OnMethodInvocationComplete(t.Result);
var result = func();
//If the call failed or returned null then we'll fire the event immediately
if (!result.Success || result.Result == null)
{
OnMethodInvocationComplete(result);
}
else
{
var resultType = result.Result.GetType();
//If the returned type is Task then we'll ContinueWith
//and perform the processing then
if (typeof(Task).IsAssignableFrom(resultType))
{
var resultTask = (Task)result.Result;
if (resultType.IsGenericType)
{
resultTask.ContinueWith((t) =>
{
if (t.Status == TaskStatus.RanToCompletion)
{
//We use some reflection to get the Result
//If someone has a better way of doing this then please submit a PR
result.Result = resultType.GetProperty("Result").GetValue(resultTask);
}
else
{
result.Success = false;
result.Result = null;
result.Message = t.Exception.ToString();
}
OnMethodInvocationComplete(result);
},
cancellationTokenSource.Token, TaskContinuationOptions.None, TaskScheduler.Default);
}
else
{
//If it's not a generic Task then it doesn't have a return object
//So we'll just set the result to null and continue on
result.Result = null;
OnMethodInvocationComplete(result);
}
}
else
{
OnMethodInvocationComplete(result);
}
}
}, cancellationTokenSource.Token);

task.Start(TaskScheduler.Default);
}
}
else
{
//Old behaviour, runs Tasks in sequential order on the current Thread.
//Old behaviour, runs the Func<MethodInvocationResult> in sequential order on the current Thread.
while (!cancellationTokenSource.IsCancellationRequested)
{
var task = queue.Take(cancellationTokenSource.Token);
task.RunSynchronously();
OnMethodInvocationComplete(task.Result);
var func = queue.Take(cancellationTokenSource.Token);

var result = func();
OnMethodInvocationComplete(result);
}
}
}
Expand Down

0 comments on commit a9f20ac

Please sign in to comment.