// 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; } } } }