// Copyright © 2020 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.Tasks;
namespace CefSharp.Internals
{
///
/// To access the CEF threads we expose a TaskFactory, as this requires managed vc++ this
/// exists in CefSharp.Core it cannot be directly accessed in CefSharp.dll. When
/// Cef.Initialized is called we pass a reference to the TaskFactory here so we
/// can write methods (typically extension methods) in this assembly.
///
/// TODO: This can likely be removed and code that depends on this can be moved
/// to CefSharp.Core and interact directly with the C++ api
public static class CefThread
{
private static readonly object LockObj = new object();
///
/// TaskFactory will be null before Cef.Initialize is called
/// and null after Cef.Shutdown is called.
///
public static TaskFactory UiThreadTaskFactory { get; private set; }
///
/// Event fired after Cef.Initialze has been called, we can now start
/// posting Tasks to the CEF UI Thread.
///
public static event EventHandler Initialized;
///
/// Delegate used to wrap the native call to CefCurrentlyOn(CefThreadId::TID_UI).
///
public static Func CurrentOnUiThreadDelegate { get; private set; }
///
/// true if we have a reference to the UiThreadTaskFactory
/// TaskFactory, otherwise false
///
///
/// The current implementation isn't thread safe, generally speaking this shouldn't be a problem
///
public static bool CanExecuteOnUiThread
{
get
{
return UiThreadTaskFactory != null;
}
}
///
/// Currently on the CEF UI Thread
///
public static bool CurrentlyOnUiThread
{
get
{
var d = CurrentOnUiThreadDelegate;
if (d == null)
{
return false;
}
return d();
}
}
///
/// returns true if Cef.Shutdown been called, otherwise false.
///
public static bool HasShutdown { get; private set; }
///
/// Execute the provided function on the CEF UI Thread
///
/// result
/// function
/// Task{Result}
public static Task ExecuteOnUiThread(Func function)
{
lock (LockObj)
{
if (HasShutdown)
{
throw new Exception("Cef.Shutdown has already been called, it's no longer possible to execute on the CEF UI Thread. Check CefThread.HasShutdown to guard against this execption");
}
var taskFactory = UiThreadTaskFactory;
if (taskFactory == null)
{
//We don't have a task factory yet, so we'll queue for execution.
return QueueForExcutionWhenUiThreadCreated(function);
}
return taskFactory.StartNew(function);
}
}
///
/// Execute the provided action on the CEF UI Thread
///
/// action
/// Task
public static Task ExecuteOnUiThread(Action action)
{
lock (LockObj)
{
if (HasShutdown)
{
throw new Exception("Cef.Shutdown has already been called, it's no longer possible to execute on the CEF UI Thread. Check CefThread.HasShutdown to guard against this execption");
}
var taskFactory = UiThreadTaskFactory;
if (taskFactory == null)
{
//We don't have a task factory yet, so we'll queue for execution.
return QueueForExcutionWhenUiThreadCreated(action);
}
return taskFactory.StartNew(action);
}
}
///
/// Wait for CEF to Initialize, continuation happens on
/// the CEF UI Thraed.
///
/// Task that can be awaited
private static Task QueueForExcutionWhenUiThreadCreated(Action action)
{
var tcs = new TaskCompletionSource();
EventHandler handler = null;
handler = (s, args) =>
{
Initialized -= handler;
try
{
//TODO: Should this call UiThreadTaskFactory.StartNew?
action();
tcs.TrySetResult(true);
}
catch(Exception ex)
{
tcs.TrySetException(ex);
}
};
Initialized += handler;
return tcs.Task;
}
///
/// Wait for CEF to Initialize, continuation happens on
/// the CEF UI Thraed.
///
/// Task that can be awaited
private static Task QueueForExcutionWhenUiThreadCreated(Func func)
{
var tcs = new TaskCompletionSource();
EventHandler handler = null;
handler = (s, args) =>
{
Initialized -= handler;
try
{
//TODO: Should this call UiThreadTaskFactory.StartNew?
var result = func();
tcs.TrySetResult(result);
}
catch(Exception ex)
{
tcs.TrySetException(ex);
}
};
Initialized += handler;
return tcs.Task;
}
///
/// Called when the CEF UI Thread is a
///
public static void Initialize(TaskFactory uiThreadTaskFactory, Func currentOnUiThreadDelegate)
{
lock (LockObj)
{
Initialized?.Invoke(null, EventArgs.Empty);
UiThreadTaskFactory = uiThreadTaskFactory;
CurrentOnUiThreadDelegate = currentOnUiThreadDelegate;
}
}
///
/// !!WARNING!! DO NOT CALL THIS YOURSELF, THIS WILL BE CALLED INTERNALLY.
/// Called when Cef.Shutdown is called to cleanup our references
/// and release any event handlers.
///
public static void Shutdown()
{
lock (LockObj)
{
CurrentOnUiThreadDelegate = null;
Initialized = null;
UiThreadTaskFactory = null;
HasShutdown = true;
}
}
}
}