diff --git a/change/react-native-windows-2020-05-25-15-56-21-PR-UIDispatcherProperty.json b/change/react-native-windows-2020-05-25-15-56-21-PR-UIDispatcherProperty.json new file mode 100644 index 00000000000..fc7f7bb93c4 --- /dev/null +++ b/change/react-native-windows-2020-05-25-15-56-21-PR-UIDispatcherProperty.json @@ -0,0 +1,8 @@ +{ + "type": "prerelease", + "comment": "Add UIDispatcher property to ReactInstanceSettings and IReactContext", + "packageName": "react-native-windows", + "email": "vmorozov@microsoft.com", + "dependentChangeType": "patch", + "date": "2020-05-25T22:56:21.049Z" +} diff --git a/packages/playground/windows/playground-win32/Playground-Win32.cpp b/packages/playground/windows/playground-win32/Playground-Win32.cpp index fa1dfc6f319..e9658fa8513 100644 --- a/packages/playground/windows/playground-win32/Playground-Win32.cpp +++ b/packages/playground/windows/playground-win32/Playground-Win32.cpp @@ -138,9 +138,6 @@ struct WindowData { host.InstanceSettings().UseFastRefresh(m_liveReloadEnabled); host.InstanceSettings().DebuggerPort(m_debuggerPort); host.InstanceSettings().RedBoxHandler(winrt::make()); - host.InstanceSettings().Properties().Set( - winrt::Microsoft::ReactNative::ReactDispatcherHelper::UIDispatcherProperty(), - winrt::Microsoft::ReactNative::ReactDispatcherHelper::UIThreadDispatcher()); // Nudge the ReactNativeHost to create the instance and wrapping context host.ReloadInstance(); diff --git a/packages/playground/windows/playground/MainPage.cpp b/packages/playground/windows/playground/MainPage.cpp index aaade330325..57ad5438409 100644 --- a/packages/playground/windows/playground/MainPage.cpp +++ b/packages/playground/windows/playground/MainPage.cpp @@ -42,9 +42,6 @@ void MainPage::OnLoadClick( host.InstanceSettings().DebuggerBreakOnNextLine(x_BreakOnFirstLineCheckBox().IsChecked().GetBoolean()); host.InstanceSettings().UseFastRefresh(x_UseFastRefreshCheckBox().IsChecked().GetBoolean()); host.InstanceSettings().DebuggerPort(static_cast(std::stoi(std::wstring(x_DebuggerPort().Text())))); - host.InstanceSettings().Properties().Set( - winrt::Microsoft::ReactNative::ReactDispatcherHelper::UIDispatcherProperty(), - winrt::Microsoft::ReactNative::ReactDispatcherHelper::UIThreadDispatcher()); // Nudge the ReactNativeHost to create the instance and wrapping context host.ReloadInstance(); diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj b/vnext/Desktop/React.Windows.Desktop.vcxproj index 9f001c9422a..872dd0aba83 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj @@ -127,6 +127,8 @@ + + ABI\MemoryTracker.idl $(IntDir)\ABI\ diff --git a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters index 36e18ade219..38e72e5d1cc 100644 --- a/vnext/Desktop/React.Windows.Desktop.vcxproj.filters +++ b/vnext/Desktop/React.Windows.Desktop.vcxproj.filters @@ -60,6 +60,7 @@ ABI + ABI @@ -81,9 +82,17 @@ ABI - + + + + + + + + + ABI @@ -133,13 +142,6 @@ Source Files - - - - - - - diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp index c7ce0a1651e..9dde8c00388 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactContextTest.cpp @@ -16,6 +16,10 @@ struct ReactContextStub : implements { VerifyElseCrashSz(false, "Not implemented"); } + IReactDispatcher UIDispatcher() noexcept { + VerifyElseCrashSz(false, "Not implemented"); + } + void DispatchEvent( xaml::FrameworkElement const & /*view*/, hstring const & /*eventName*/, diff --git a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h index e49548661b7..f534a32ffba 100644 --- a/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h +++ b/vnext/Microsoft.ReactNative.Cxx.UnitTests/ReactModuleBuilderMock.h @@ -119,6 +119,10 @@ struct ReactContextMock : implements { VerifyElseCrashSz(false, "Not implemented"); } + IReactDispatcher UIDispatcher() noexcept { + VerifyElseCrashSz(false, "Not implemented"); + } + void DispatchEvent( xaml::FrameworkElement const & /*view*/, hstring const & /*eventName*/, diff --git a/vnext/Microsoft.ReactNative.Cxx/ReactContext.h b/vnext/Microsoft.ReactNative.Cxx/ReactContext.h index 433abe83e2b..f881313bb83 100644 --- a/vnext/Microsoft.ReactNative.Cxx/ReactContext.h +++ b/vnext/Microsoft.ReactNative.Cxx/ReactContext.h @@ -37,6 +37,10 @@ struct ReactContext { return ReactNotificationService{m_handle.Notifications()}; } + ReactDispatcher UIDispatcher() const noexcept { + return ReactDispatcher{m_handle.UIDispatcher()}; + } + // Call methodName JS function of module with moduleName. // args are either function arguments or a single lambda with 'IJSValueWriter const&' argument. template diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj index 51638367dc2..3a2d66838e4 100644 --- a/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj +++ b/vnext/Microsoft.ReactNative.IntegrationTests/Microsoft.ReactNative.IntegrationTests.vcxproj @@ -107,6 +107,7 @@ + diff --git a/vnext/Microsoft.ReactNative.IntegrationTests/ReactInstanceSettingsTests.cpp b/vnext/Microsoft.ReactNative.IntegrationTests/ReactInstanceSettingsTests.cpp new file mode 100644 index 00000000000..9a0f48cbba8 --- /dev/null +++ b/vnext/Microsoft.ReactNative.IntegrationTests/ReactInstanceSettingsTests.cpp @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +#include "pch.h" + +#include +#include + +using namespace winrt; +using namespace Microsoft::ReactNative; +using namespace Windows::System; + +namespace ReactNativeIntegrationTests { + +TEST_CLASS (ReactInstanceSettingsTests) { + TEST_METHOD(DefaultUIDispatcher_NonUIThread) { + // In case if current thread has no ThreadDispatcher, then the + // ReactInstanceSettings::UIDispatcher is not initialized. + ReactInstanceSettings settings; + TestCheck(settings); + TestCheck(!settings.UIDispatcher()); + // UIDispatcher() is a shortcut for getting property value. + TestCheck(!settings.Properties().Get(ReactDispatcherHelper::UIDispatcherProperty())); + } + + TEST_METHOD(DefaultUIDispatcher_UIThread) { + // ReactInstanceSettings::UIDispatcher is set to a non-null value if it is + // creates from a UI thread. We simulate the UI thread with DispatcherQueueController. + auto queueController = DispatcherQueueController::CreateOnDedicatedThread(); + auto uiDispatcher = queueController.DispatcherQueue(); + TestCheck(uiDispatcher); + uiDispatcher.TryEnqueue([]() noexcept { + ReactInstanceSettings settings; + TestCheck(settings); + TestCheck(settings.UIDispatcher()); + // UIDispatcher() is a shortcut for getting property value. + TestCheck(settings.Properties().Get(ReactDispatcherHelper::UIDispatcherProperty())); + TestCheckEqual(ReactDispatcherHelper::UIThreadDispatcher(), settings.UIDispatcher()); + TestCheckEqual( + ReactDispatcherHelper::UIThreadDispatcher(), + settings.Properties().Get(ReactDispatcherHelper::UIDispatcherProperty())); + }); + queueController.ShutdownQueueAsync().get(); + } +}; + +} // namespace ReactNativeIntegrationTests diff --git a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs index b41ef2d2c80..3081a082e1a 100644 --- a/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs +++ b/vnext/Microsoft.ReactNative.Managed.UnitTests/ReactModuleBuilderMock.cs @@ -309,6 +309,8 @@ public ReactContextMock(ReactModuleBuilderMock builder) public IReactNotificationService Notifications { get; } = ReactNotificationServiceHelper.CreateNotificationService(); + public IReactDispatcher UIDispatcher => Properties.Get(ReactDispatcherHelper.UIDispatcherProperty) as IReactDispatcher; + public void DispatchEvent(FrameworkElement view, string eventName, JSValueArgWriter eventDataArgWriter) { throw new NotImplementedException(); diff --git a/vnext/Microsoft.ReactNative/ABIViewManager.cpp b/vnext/Microsoft.ReactNative/ABIViewManager.cpp index 5cecd2ec457..72081783e70 100644 --- a/vnext/Microsoft.ReactNative/ABIViewManager.cpp +++ b/vnext/Microsoft.ReactNative/ABIViewManager.cpp @@ -27,7 +27,7 @@ ABIViewManager::ABIViewManager( m_viewManagerWithChildren{viewManager.try_as()}, m_name{to_string(viewManager.Name())} { if (m_viewManagerWithReactContext) { - m_viewManagerWithReactContext.ReactContext(winrt::make(Mso::Copy(reactContext))); + m_viewManagerWithReactContext.ReactContext(winrt::make(Mso::Copy(reactContext))); } if (m_viewManagerWithNativeProperties) { m_nativeProps = m_viewManagerWithNativeProperties.NativeProps(); diff --git a/vnext/Microsoft.ReactNative/IReactContext.cpp b/vnext/Microsoft.ReactNative/IReactContext.cpp index a232d780be6..c14fedd3bc6 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.cpp +++ b/vnext/Microsoft.ReactNative/IReactContext.cpp @@ -5,7 +5,7 @@ #include "IReactContext.h" #include "DynamicWriter.h" -namespace winrt::Microsoft::ReactNative { +namespace winrt::Microsoft::ReactNative::implementation { ReactContext::ReactContext(Mso::CntPtr &&context) noexcept : m_context{std::move(context)} {} @@ -17,6 +17,10 @@ IReactNotificationService ReactContext::Notifications() noexcept { return m_context->Notifications(); } +IReactDispatcher ReactContext::UIDispatcher() noexcept { + return Properties().Get(ReactDispatcherHelper::UIDispatcherProperty()).try_as(); +} + void ReactContext::DispatchEvent( xaml::FrameworkElement const &view, hstring const &eventName, @@ -55,4 +59,4 @@ void ReactContext::EmitJSEvent( m_context->CallJSFunction(to_string(eventEmitterName), "emit", std::move(params)); } -} // namespace winrt::Microsoft::ReactNative +} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/IReactContext.h b/vnext/Microsoft.ReactNative/IReactContext.h index 0dd2069a7d2..4abc0c67b08 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.h +++ b/vnext/Microsoft.ReactNative/IReactContext.h @@ -6,7 +6,7 @@ #include "ReactHost/React.h" #include "winrt/Microsoft.ReactNative.h" -namespace winrt::Microsoft::ReactNative { +namespace winrt::Microsoft::ReactNative::implementation { struct ReactContext : winrt::implements { ReactContext(Mso::CntPtr &&context) noexcept; @@ -14,6 +14,7 @@ struct ReactContext : winrt::implements { public: // IReactContext IReactPropertyBag Properties() noexcept; IReactNotificationService Notifications() noexcept; + IReactDispatcher UIDispatcher() noexcept; void DispatchEvent( xaml::FrameworkElement const &view, hstring const &eventName, @@ -31,4 +32,4 @@ struct ReactContext : winrt::implements { Mso::CntPtr m_context; }; -} // namespace winrt::Microsoft::ReactNative +} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/IReactContext.idl b/vnext/Microsoft.ReactNative/IReactContext.idl index 312d5148636..119ffeff6b8 100644 --- a/vnext/Microsoft.ReactNative/IReactContext.idl +++ b/vnext/Microsoft.ReactNative/IReactContext.idl @@ -26,6 +26,9 @@ namespace Microsoft.ReactNative { // The subscriptions added to IReactInstanceSettings.Notifications kept as long as IReactInstanceSettings alive. IReactNotificationService Notifications { get; }; + // Get ReactDispatcherHelper::UIDispatcherProperty from the Properties property bag. + IReactDispatcher UIDispatcher { get; }; + // Dispatch UI event. This method is to be moved to IReactViewContext. void DispatchEvent(XAML_NAMESPACE.FrameworkElement view, String eventName, JSValueArgWriter eventDataArgWriter); diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.cpp b/vnext/Microsoft.ReactNative/IReactDispatcher.cpp index f8910d9858e..5ff76866ecc 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.cpp +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.cpp @@ -29,20 +29,40 @@ void ReactDispatcher::Post(ReactDispatcherCallback const &callback) noexcept { } /*static*/ IReactDispatcher ReactDispatcher::UIThreadDispatcher() noexcept { - return make(Mso::DispatchQueue::MakeCurrentThreadUIQueue()); + static thread_local weak_ref *tlsWeakDispatcher{nullptr}; + IReactDispatcher dispatcher{nullptr}; + auto queue = Mso::DispatchQueue::GetCurrentUIThreadQueue(); + if (queue && queue.HasThreadAccess()) { + queue.InvokeElsePost([&queue, &dispatcher ]() noexcept { + // This code runs synchronously, but we want it to be run the queue context to + // access the queue local value where we store the weak_ref to the dispatcher. + // The queue local values are destroyed along with the queue. + // To access queue local value we temporary swap it with the thread local value. + // It must be a TLS value to ensure proper indexing of the queue local value entry. + auto tlsGuard{queue.LockLocalValue(&tlsWeakDispatcher)}; + dispatcher = tlsWeakDispatcher->get(); + if (!dispatcher) { + dispatcher = winrt::make(std::move(queue)); + *tlsWeakDispatcher = dispatcher; + } + }); + } + + return dispatcher; } -/*static*/ ReactPropertyId ReactDispatcher::UIDispatcherProperty() noexcept { - static ReactPropertyId uiThreadDispatcherProperty{L"ReactNative.Dispatcher", L"UIDispatcher"}; +/*static*/ IReactPropertyName ReactDispatcher::UIDispatcherProperty() noexcept { + static IReactPropertyName uiThreadDispatcherProperty{ReactPropertyBagHelper::GetName( + ReactPropertyBagHelper::GetNamespace(L"ReactNative.Dispatcher"), L"UIDispatcher")}; return uiThreadDispatcherProperty; } /*static*/ IReactDispatcher ReactDispatcher::GetUIDispatcher(IReactPropertyBag const &properties) noexcept { - return ReactPropertyBag{properties}.Get(UIDispatcherProperty()); + return properties.Get(UIDispatcherProperty()).try_as(); } /*static*/ void ReactDispatcher::SetUIThreadDispatcher(IReactPropertyBag const &properties) noexcept { - ReactPropertyBag{properties}.Set(UIDispatcherProperty(), UIThreadDispatcher()); + properties.Set(UIDispatcherProperty(), UIThreadDispatcher()); } } // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.h b/vnext/Microsoft.ReactNative/IReactDispatcher.h index 0cd85bf4ca8..99cdda6b11c 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.h +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.h @@ -3,13 +3,12 @@ #pragma once #include "ReactDispatcherHelper.g.h" -#include #include #include namespace winrt::Microsoft::ReactNative::implementation { -struct ReactDispatcher : implements { +struct ReactDispatcher : implements> { ReactDispatcher() = default; ReactDispatcher(Mso::DispatchQueue &&queue) noexcept; @@ -21,7 +20,7 @@ struct ReactDispatcher : implements { static Mso::DispatchQueue GetUIDispatchQueue(IReactPropertyBag const &properties) noexcept; static IReactDispatcher UIThreadDispatcher() noexcept; - static ReactPropertyId UIDispatcherProperty() noexcept; + static IReactPropertyName UIDispatcherProperty() noexcept; static IReactDispatcher GetUIDispatcher(IReactPropertyBag const &properties) noexcept; static void SetUIThreadDispatcher(IReactPropertyBag const &properties) noexcept; @@ -41,7 +40,7 @@ struct ReactDispatcherHelper { } static IReactPropertyName UIDispatcherProperty() noexcept { - return ReactDispatcher::UIDispatcherProperty().Handle(); + return ReactDispatcher::UIDispatcherProperty(); } }; diff --git a/vnext/Microsoft.ReactNative/IReactDispatcher.idl b/vnext/Microsoft.ReactNative/IReactDispatcher.idl index 84dd744f5b7..d9ef4576c81 100644 --- a/vnext/Microsoft.ReactNative/IReactDispatcher.idl +++ b/vnext/Microsoft.ReactNative/IReactDispatcher.idl @@ -30,6 +30,6 @@ namespace Microsoft.ReactNative { static IReactDispatcher UIThreadDispatcher{ get; }; // Get name of the UIDispatcher property for the IReactPropertyBag. - static IReactPropertyName UIDispatcherProperty(); + static IReactPropertyName UIDispatcherProperty { get; }; } } // namespace ReactNative diff --git a/vnext/Microsoft.ReactNative/NativeModulesProvider.cpp b/vnext/Microsoft.ReactNative/NativeModulesProvider.cpp index 25c9199bfb2..566891c6cd8 100644 --- a/vnext/Microsoft.ReactNative/NativeModulesProvider.cpp +++ b/vnext/Microsoft.ReactNative/NativeModulesProvider.cpp @@ -34,7 +34,7 @@ std::vector NativeModulesProvider::Get m_modulesWorkerQueue = react::uwp::MakeSerialQueueThread(); } - auto winrtReactContext = winrt::make(Mso::Copy(reactContext)); + auto winrtReactContext = winrt::make(Mso::Copy(reactContext)); for (auto &entry : m_moduleProviders) { modules.emplace_back( diff --git a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp index c63e62c4f56..14a73d3837a 100644 --- a/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp +++ b/vnext/Microsoft.ReactNative/ReactHost/ReactInstanceWin.cpp @@ -503,6 +503,7 @@ void ReactInstanceWin::InitNativeMessageThread() noexcept { void ReactInstanceWin::InitUIMessageThread() noexcept { // Native queue was already given us in constructor. m_uiQueue = winrt::Microsoft::ReactNative::implementation::ReactDispatcher::GetUIDispatchQueue(m_options.Properties); + VerifyElseCrashSz(m_uiQueue, "No UI Dispatcher provided"); m_uiMessageThread.Exchange( std::make_shared(m_uiQueue, Mso::MakeWeakMemberFunctor(this, &ReactInstanceWin::OnError))); diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp b/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp index f1bc040699b..98d58b8c98b 100644 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp +++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.cpp @@ -7,4 +7,19 @@ #include "ReactInstanceSettings.g.cpp" #endif -namespace winrt::Microsoft::ReactNative::implementation {} // namespace winrt::Microsoft::ReactNative::implementation +namespace winrt::Microsoft::ReactNative::implementation { + +ReactInstanceSettings::ReactInstanceSettings() noexcept { + // Use current thread dispatcher as a default UI dispatcher. + m_properties.Set(ReactDispatcherHelper::UIDispatcherProperty(), ReactDispatcherHelper::UIThreadDispatcher()); +} + +IReactDispatcher ReactInstanceSettings::UIDispatcher() noexcept { + return m_properties.Get(ReactDispatcherHelper::UIDispatcherProperty()).try_as(); +} + +void ReactInstanceSettings::UIDispatcher(IReactDispatcher const &value) noexcept { + m_properties.Set(ReactDispatcherHelper::UIDispatcherProperty(), value); +} + +} // namespace winrt::Microsoft::ReactNative::implementation diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.h b/vnext/Microsoft.ReactNative/ReactInstanceSettings.h index 154c4b99a79..ee3f4644dae 100644 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.h +++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.h @@ -22,7 +22,7 @@ namespace winrt::Microsoft::ReactNative::implementation { struct ReactInstanceSettings : ReactInstanceSettingsT { - ReactInstanceSettings() = default; + ReactInstanceSettings() noexcept; IReactPropertyBag Properties() noexcept; @@ -85,6 +85,9 @@ struct ReactInstanceSettings : ReactInstanceSettingsT { IRedBoxHandler RedBoxHandler() noexcept; void RedBoxHandler(IRedBoxHandler const &value) noexcept; + IReactDispatcher UIDispatcher() noexcept; + void UIDispatcher(IReactDispatcher const &value) noexcept; + hstring SourceBundleHost() noexcept; void SourceBundleHost(hstring const &value) noexcept; diff --git a/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl b/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl index 8153ba09692..2e4f3d69e2e 100644 --- a/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl +++ b/vnext/Microsoft.ReactNative/ReactInstanceSettings.idl @@ -1,9 +1,10 @@ // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. -import "RedBoxHandler.idl"; +import "IReactDispatcher.idl"; import "IReactNotificationService.idl"; import "IReactPropertyBag.idl"; +import "RedBoxHandler.idl"; namespace Microsoft.ReactNative { @@ -33,6 +34,7 @@ namespace Microsoft.ReactNative { String BundleRootPath { get; set; }; UInt16 DebuggerPort { get; set; }; IRedBoxHandler RedBoxHandler { get; set; }; + IReactDispatcher UIDispatcher { get; set; }; String SourceBundleHost { get; set; }; UInt16 SourceBundlePort { get; set; }; } diff --git a/vnext/Microsoft.ReactNative/Threading/MessageQueueThreadFactory.cpp b/vnext/Microsoft.ReactNative/Threading/MessageQueueThreadFactory.cpp index 1dfd9e4f3c5..803369b3f7f 100644 --- a/vnext/Microsoft.ReactNative/Threading/MessageQueueThreadFactory.cpp +++ b/vnext/Microsoft.ReactNative/Threading/MessageQueueThreadFactory.cpp @@ -12,8 +12,10 @@ std::shared_ptr MakeJSQueueThread() noexcep } std::shared_ptr MakeUIQueueThread() noexcept { - return std::make_shared( - Mso::DispatchQueue::MakeCurrentThreadUIQueue(), nullptr, nullptr); + Mso::DispatchQueue queue = Mso::DispatchQueue::GetCurrentUIThreadQueue(); + std::shared_ptr messageThread = + queue ? std::make_shared(queue, nullptr, nullptr) : nullptr; + return messageThread; } std::shared_ptr MakeSerialQueueThread() noexcept { diff --git a/vnext/Microsoft.ReactNative/Views/ReactRootControl.cpp b/vnext/Microsoft.ReactNative/Views/ReactRootControl.cpp index b094e1388c9..d5250dd46eb 100644 --- a/vnext/Microsoft.ReactNative/Views/ReactRootControl.cpp +++ b/vnext/Microsoft.ReactNative/Views/ReactRootControl.cpp @@ -42,7 +42,8 @@ namespace react::uwp { //=========================================================================== ReactRootControl::ReactRootControl(XamlView const &rootView) noexcept - : m_weakRootView{rootView}, m_uiQueue(Mso::DispatchQueue::MakeCurrentThreadUIQueue()) { + : m_weakRootView{rootView}, m_uiQueue(Mso::DispatchQueue::GetCurrentUIThreadQueue()) { + VerifyElseCrashSz(m_uiQueue, "Cannot get UI dispatch queue for the current thread"); PrepareXamlRootView(rootView); } diff --git a/vnext/Mso/dispatchQueue/dispatchQueue.h b/vnext/Mso/dispatchQueue/dispatchQueue.h index 50e2ecbe1e0..c37efbb1b14 100644 --- a/vnext/Mso/dispatchQueue/dispatchQueue.h +++ b/vnext/Mso/dispatchQueue/dispatchQueue.h @@ -48,7 +48,7 @@ enum class PendingTaskAction { }; //! Callback type to handle queue local values -using SwapDispatchLocalValueCallback = void (*)(void **localValue, void *tlsValue) noexcept; +using SwapDispatchLocalValueCallback = void (*)(void **localValue, void **tlsValue) noexcept; //! Gets a pointer to the state storage. This is a 'back-door' for advanced scenarios. Use with caution! template @@ -58,7 +58,7 @@ auto GetRawState(TObject &&obj) noexcept; //! It creates new value if localValue is null. //! It destroys local value if tlsValue is null. template -void SwapDispatchLocalValue(void **localValue, void *tlsValue) noexcept; +void SwapDispatchLocalValue(void **localValue, void **tlsValue) noexcept; template Mso::VoidFunctor MakeDispatchTask(TInvoke &&invoke, TOnCancel &&onCancel) noexcept; @@ -69,7 +69,7 @@ Mso::VoidFunctor MakeDispatchCleanupTask(TInvoke &&invoke) noexcept; //! RAII class to unlock the queue local value by swapping it back with TLS variable. struct DispatchLocalValueGuard { //! Create new DispatchLocalValueGuard instance and swap TLS value with the queue local value. - DispatchLocalValueGuard(IDispatchQueueService *queue, void *tlsValue) noexcept; + DispatchLocalValueGuard(IDispatchQueueService *queue, void **tlsValue) noexcept; //! Swap back the TLS value with the queue local value. It must run on the same thread as the constructor. ~DispatchLocalValueGuard() noexcept; @@ -82,7 +82,7 @@ struct DispatchLocalValueGuard { private: IDispatchQueueService *m_queue; - void *m_tlsValue; + void **m_tlsValue; }; //! Serial or concurrent dispatch queue main API. @@ -119,8 +119,9 @@ struct DispatchQueue { //! Create new looper DispatchQueue on top of new std::thread. It owns the thread until shutdown. static DispatchQueue MakeLooperQueue() noexcept; - //! Create new UI DispatchQueue for the current UI thread. - static DispatchQueue MakeCurrentThreadUIQueue() noexcept; + //! Get a dispatch queue for the current UI thread. The result is null if the UI thread has no system UI thread + //! dispatcher. + static DispatchQueue GetCurrentUIThreadQueue() noexcept; //! Create a concurrent queue on top of platform specific thread pool that uses up to maxThreads threads. //! If maxThreads is zero, then it creates a concurrent queue that has a predefined limit on concurrently submitted @@ -174,7 +175,7 @@ struct DispatchQueue { //! The returned DispatchLocalValueGuard returns true when casted to bool in case of success. It can be used inside of //! an 'if' statement. template - DispatchLocalValueGuard LockLocalValue(TValue *tlsValue) const noexcept; + DispatchLocalValueGuard LockLocalValue(TValue **tlsValue) const noexcept; //! Suspend asynchronous task invocation and return a guard that resumes it in its destructor. DispatchSuspendGuard Suspend() const noexcept; @@ -361,10 +362,10 @@ struct IDispatchQueueService : IUnknown { //! Unlocking the QLV swaps it back with the TLS variable. //! On the first call the variable will be created by swapLocalValue. When queue is shutdown the variable is destroyed //! by swapLocalValue. The TLS variable address corresponding to the QLV is used to define a key for storing QLV. - virtual bool TryLockQueueLocalValue(SwapDispatchLocalValueCallback swapLocalValue, void *tlsValue) noexcept = 0; + virtual bool TryLockQueueLocalValue(SwapDispatchLocalValueCallback swapLocalValue, void **tlsValue) noexcept = 0; //! Unlock the queue local value by swapping it back with the TLS value. - virtual void UnlockLocalValue(void *tlsValue) noexcept = 0; + virtual void UnlockLocalValue(void **tlsValue) noexcept = 0; //! Suspend task invocation and increment internal suspend count by one. //! Note that we do not expose IsSuspeneded() method because the Suspend/Resume @@ -418,8 +419,9 @@ struct IDispatchQueueStatic : IUnknown { //! Create new looper DispatchQueue on top of new std::thread. It owns the thread until shutdown. virtual DispatchQueue MakeLooperQueue() noexcept = 0; - //! Creates new UI DispatchQueue for the current UI thread. - virtual DispatchQueue MakeCurrentThreadUIQueue() noexcept = 0; + //! Get a dispatch queue for the current UI thread. The result is null if the UI thread has no system UI thread + //! dispatcher. + virtual DispatchQueue GetCurrentUIThreadQueue() noexcept = 0; //! Create a concurrent queue on top of platform specific thread pool that uses up to maxThreads threads. //! If maxThreads is zero, then it creates a concurrent queue that has a predefined limit on concurrently submitted @@ -474,17 +476,18 @@ inline auto GetRawState(TObject &&obj) noexcept { } template -void SwapQueueLocalValue(void **localValue, void *tlsValue) noexcept { +void SwapDispatchLocalValue(void **localValue, void **tlsValue) noexcept { if (!tlsValue) { // Delete the queue local value when tlsValue is null. if (*localValue) { delete static_cast(*localValue); *localValue = nullptr; } - } else if (!*localValue) { // Initialize queue local value on the first call + return; + } else if (!*tlsValue && !*localValue) { // Initialize queue local value on the first call *localValue = new TValue{}; } using std::swap; - swap(*static_cast(*localValue), *static_cast(tlsValue)); + swap(*localValue, *tlsValue); } template @@ -504,7 +507,7 @@ inline Mso::VoidFunctor MakeDispatchCleanupTask(TInvoke &&invoke) noexcept { // DispatchLocalValueGuard inline implementation //============================================================================= -inline DispatchLocalValueGuard::DispatchLocalValueGuard(IDispatchQueueService *queue, void *tlsValue) noexcept +inline DispatchLocalValueGuard::DispatchLocalValueGuard(IDispatchQueueService *queue, void **tlsValue) noexcept : m_queue{queue}, m_tlsValue{tlsValue} {} inline DispatchLocalValueGuard::~DispatchLocalValueGuard() noexcept { @@ -546,8 +549,8 @@ inline /*static*/ DispatchQueue DispatchQueue::MakeLooperQueue() noexcept { return IDispatchQueueStatic::Instance()->MakeLooperQueue(); } -inline /*static*/ DispatchQueue DispatchQueue::MakeCurrentThreadUIQueue() noexcept { - return IDispatchQueueStatic::Instance()->MakeCurrentThreadUIQueue(); +inline /*static*/ DispatchQueue DispatchQueue::GetCurrentUIThreadQueue() noexcept { + return IDispatchQueueStatic::Instance()->GetCurrentUIThreadQueue(); } inline /*static*/ DispatchQueue DispatchQueue::MakeConcurrentQueue(uint32_t maxThreads) noexcept { @@ -596,9 +599,9 @@ inline DispatchTaskBatch DispatchQueue::StartTaskBatching() const noexcept { } template -inline DispatchLocalValueGuard DispatchQueue::LockLocalValue(TValue *tlsValue) const noexcept { - if (m_state->TryLockQueueLocalValue(SwapDispatchLocalValue, tlsValue)) { - return DispatchLocalValueGuard{m_state.Get(), tlsValue}; +inline DispatchLocalValueGuard DispatchQueue::LockLocalValue(TValue **tlsValue) const noexcept { + if (m_state->TryLockQueueLocalValue(SwapDispatchLocalValue, (void **)tlsValue)) { + return DispatchLocalValueGuard{m_state.Get(), (void **)tlsValue}; } else { return DispatchLocalValueGuard{nullptr, nullptr}; } diff --git a/vnext/Mso/src/dispatchQueue/queueService.cpp b/vnext/Mso/src/dispatchQueue/queueService.cpp index d30f444bdc0..b31b7f5de2e 100644 --- a/vnext/Mso/src/dispatchQueue/queueService.cpp +++ b/vnext/Mso/src/dispatchQueue/queueService.cpp @@ -122,17 +122,17 @@ bool QueueService::HasTaskBatching() noexcept { return m_taskBatches.find(std::this_thread::get_id()) != m_taskBatches.end(); } -bool QueueService::TryLockQueueLocalValue(SwapDispatchLocalValueCallback swapLocalValue, void *tlsValue) noexcept { - return TrySwapLocalValue(swapLocalValue, tlsValue, LocalValueSwapAction::Unlock); +bool QueueService::TryLockQueueLocalValue(SwapDispatchLocalValueCallback swapLocalValue, void **tlsValue) noexcept { + return TrySwapLocalValue(swapLocalValue, tlsValue, LocalValueSwapAction::Lock); } -void QueueService::UnlockLocalValue(void *tlsValue) noexcept { +void QueueService::UnlockLocalValue(void **tlsValue) noexcept { TrySwapLocalValue(nullptr, tlsValue, LocalValueSwapAction::Unlock); } bool QueueService::TrySwapLocalValue( SwapDispatchLocalValueCallback swapLocalValue, - void *tlsValue, + void **tlsValue, LocalValueSwapAction action) noexcept { std::lock_guard lock{m_mutex}; @@ -140,7 +140,7 @@ bool QueueService::TrySwapLocalValue( // layout between threads. We use address delta between the provided TLS value and // our internal localValueAnchor. It must be the same for a TLS value between threads. static thread_local uint8_t localValueAnchor{0}; - ptrdiff_t key = static_cast(tlsValue) - &localValueAnchor; + ptrdiff_t key = static_cast(static_cast(tlsValue)) - &localValueAnchor; auto [it, added] = m_localValues.try_emplace(key, swapLocalValue); return it->second.TrySwapLocalValue(tlsValue, action); } @@ -230,7 +230,7 @@ QueueLocalValueEntry::~QueueLocalValueEntry() noexcept { m_swapLocalValue(&m_data, nullptr); } -bool QueueLocalValueEntry::TrySwapLocalValue(void *tlsValue, LocalValueSwapAction action) noexcept { +bool QueueLocalValueEntry::TrySwapLocalValue(void **tlsValue, LocalValueSwapAction action) noexcept { if (action == LocalValueSwapAction::Lock && !m_isLocked) { m_isLocked = true; } else if (action == LocalValueSwapAction::Unlock && m_isLocked) { @@ -277,10 +277,6 @@ DispatchQueue DispatchQueueStatic::MakeLooperQueue() noexcept { return Mso::Make(MakeLooperScheduler()); } -DispatchQueue DispatchQueueStatic::MakeCurrentThreadUIQueue() noexcept { - return Mso::Make(MakeCurrentThreadUIScheduler()); -} - DispatchQueue DispatchQueueStatic::MakeConcurrentQueue(uint32_t maxThreads) noexcept { return Mso::Make(MakeThreadPoolScheduler(maxThreads)); } diff --git a/vnext/Mso/src/dispatchQueue/queueService.h b/vnext/Mso/src/dispatchQueue/queueService.h index a80f72a669b..0a609d3ed9b 100644 --- a/vnext/Mso/src/dispatchQueue/queueService.h +++ b/vnext/Mso/src/dispatchQueue/queueService.h @@ -40,8 +40,8 @@ struct QueueService : Mso::UnknownObject { static DispatchQueueStatic *Instance() noexcept; static Mso::CntPtr MakeLooperScheduler() noexcept; - static Mso::CntPtr MakeCurrentThreadUIScheduler() noexcept; static Mso::CntPtr MakeThreadPoolScheduler(uint32_t maxThreads) noexcept; public: // IDispatchQueueStatic @@ -90,7 +89,7 @@ struct DispatchQueueStatic : Mso::UnknownObject &&scheduler) noexcept override; }; diff --git a/vnext/Mso/src/dispatchQueue/uiScheduler_winrt.cpp b/vnext/Mso/src/dispatchQueue/uiScheduler_winrt.cpp index 70981a780a1..b548b486d5f 100644 --- a/vnext/Mso/src/dispatchQueue/uiScheduler_winrt.cpp +++ b/vnext/Mso/src/dispatchQueue/uiScheduler_winrt.cpp @@ -5,13 +5,61 @@ #include "object/refCountedObject.h" #include "queueService.h" #include "taskQueue.h" +#include "winrt/Windows.Foundation.h" #include "winrt/Windows.System.h" using namespace winrt; +using namespace Windows::Foundation; using namespace Windows::System; namespace Mso { +namespace { + +// TODO: consider to move it into its own liblet +template +struct ThreadSafeMap { + ThreadSafeMap(std::recursive_mutex &mutex) noexcept : m_mutex{mutex} {} + + std::optional Get(TKey const &key) noexcept { + std::scoped_lock lock{m_mutex}; + auto it = m_map.find(key); + if (it != m_map.end()) { + return std::optional{it->second}; + } else { + return std::optional{}; + } + } + + void Set(TKey const &key, TValue &&value) noexcept { + TValue oldValue; // to destroy outside of lock + { + std::scoped_lock lock{m_mutex}; + auto &valueRef = m_map[key]; + oldValue = std::move(valueRef); + valueRef = std::move(value); + } + } + + void Remove(TKey const &key) noexcept { + TValue value; // to destroy outside of lock + { + std::scoped_lock lock{m_mutex}; + auto it = m_map.find(key); + if (it != m_map.end()) { + value = std::move(it->second); + m_map.erase(it); + } + } + } + + private: + std::recursive_mutex &m_mutex; + std::map> m_map; +}; + +} // namespace + struct UISchedulerWinRT; //! TaskDispatcherHandler is a DispatcherQueueHandler delegate that we pass to DispatcherQueue. @@ -37,6 +85,10 @@ struct UISchedulerWinRT : Mso::UnknownObject &queue, DispatchTask &task) noexcept; + static DispatchQueue GetOrCreateUIThreadQueue() noexcept; + using DispatchQueueRegistry = ThreadSafeMap>; + static DispatchQueueRegistry &GetDispatchQueueRegistry() noexcept; + public: // IDispatchQueueScheduler void IntializeScheduler(Mso::WeakPtr &&queue) noexcept override; bool HasThreadAccess() noexcept override; @@ -46,6 +98,8 @@ struct UISchedulerWinRT : Mso::UnknownObject> weakQueue = GetDispatchQueueRegistry().Get(threadId); + DispatchQueue queue{weakQueue.value_or(nullptr).GetStrongPtr()}; + if (queue) { + return queue; + } + + auto dispatcher = DispatcherQueue::GetForCurrentThread(); + if (!dispatcher) { + return queue; + } + + queue = + DispatchQueue(Mso::Make(Mso::Make(std::move(dispatcher)))); + GetDispatchQueueRegistry().Set(threadId, Mso::WeakPtr{*GetRawState(queue)}); + return queue; +} + +/*static*/ UISchedulerWinRT::DispatchQueueRegistry &UISchedulerWinRT::GetDispatchQueueRegistry() noexcept { + static std::recursive_mutex mutex; + static DispatchQueueRegistry registry{mutex}; + return registry; +} + //============================================================================= // UISchedulerWinRT::CleanupContext implementation //============================================================================= @@ -248,8 +334,8 @@ void UISchedulerWinRT::CleanupContext::CheckTermination() noexcept { // DispatchQueueStatic::MakeCurrentThreadUIScheduler implementation //============================================================================= -/*static*/ Mso::CntPtr DispatchQueueStatic::MakeCurrentThreadUIScheduler() noexcept { - return Mso::Make(DispatcherQueue::GetForCurrentThread()); +DispatchQueue DispatchQueueStatic::GetCurrentUIThreadQueue() noexcept { + return UISchedulerWinRT::GetOrCreateUIThreadQueue(); } } // namespace Mso