// Copyright © 2019 The CefSharp Authors. All rights reserved.
//
// Use of this source code is governed by a BSD-style license that can be found in the LICENSE file.
using System;
using System.Threading;
using System.Threading.Tasks;
namespace CefSharp.Internals
{
///
/// ConcurrentMethodRunnerQueue - Async Javascript Binding methods are run
/// on the ThreadPool in parallel, when a method returns a Task
/// the we use ContinueWith to be notified of completion then
/// raise the MethodInvocationComplete event
///
public class ConcurrentMethodRunnerQueue : IMethodRunnerQueue
{
private readonly JavascriptObjectRepository repository;
private CancellationTokenSource cancellationTokenSource = new CancellationTokenSource();
public event EventHandler MethodInvocationComplete;
public ConcurrentMethodRunnerQueue(JavascriptObjectRepository repository)
{
this.repository = repository;
}
public void Dispose()
{
cancellationTokenSource.Cancel();
}
public void Enqueue(MethodInvocation methodInvocation)
{
var task = new Task(() =>
{
var result = ExecuteMethodInvocation(methodInvocation);
//If the call failed or returned null then we'll fire the event immediately
if (!result.Success || result.Result == null)
{
OnMethodInvocationComplete(result, cancellationTokenSource.Token);
}
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;
var aggregateException = t.Exception;
//TODO: Add support for passing a more complex message
// to better represent the Exception
if (aggregateException.InnerExceptions.Count == 1)
{
result.Message = aggregateException.InnerExceptions[0].ToString();
}
else
{
result.Message = t.Exception.ToString();
}
}
OnMethodInvocationComplete(result, cancellationTokenSource.Token);
},
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, cancellationTokenSource.Token);
}
}
else
{
OnMethodInvocationComplete(result, cancellationTokenSource.Token);
}
}
}, cancellationTokenSource.Token);
task.Start(TaskScheduler.Default);
}
private MethodInvocationResult ExecuteMethodInvocation(MethodInvocation methodInvocation)
{
object result = null;
string exception;
var success = false;
//make sure we don't throw exceptions in the executor task
try
{
success = repository.TryCallMethod(methodInvocation.ObjectId, methodInvocation.MethodName, methodInvocation.Parameters.ToArray(), out result, out exception);
}
catch (Exception e)
{
exception = e.Message;
}
return new MethodInvocationResult
{
BrowserId = methodInvocation.BrowserId,
CallbackId = methodInvocation.CallbackId,
FrameId = methodInvocation.FrameId,
Message = exception,
Result = result,
Success = success
};
}
private void OnMethodInvocationComplete(MethodInvocationResult e, CancellationToken token)
{
//If cancellation has been requested we don't need to continue.
if (!token.IsCancellationRequested)
{
MethodInvocationComplete?.Invoke(this, new MethodInvocationCompleteArgs(e));
}
}
}
}