Fix racy access to _tsfTryRedrawCanvas in TermControl (#10549)

Previously `TermControl::Close` destroyed all `ThrottledFunc`s to ensure they're not scheduling any callbacks on the UI thread, as the call to `Close` signals the point at which the `TermControl` isn't part of the UI thread anymore. `_CursorPositionChanged` tried to prevent access to the potentially deallocated `_tsfTryRedrawCanvas` by checking the `std::shared_ptr` for nullability, but since the deallocation happens on the UI thread and the nullability check on a background thread, this check introduced a race condition.

This commit solves the issue by not deallocating any `ThrottledFunc`s anymore and instead checking the `_closing` flag inside the `ThrottledFunc` callback on the UI thread.

Additionally this commit cleans up some antipatterns around the use of `std::optional`.

## PR Checklist
* [x] Closes #10479
* [x] Closes #10302

## Validation Steps Performed

* Opening and closing tabs doesn't crash ✔️
* Printing long text doesn't crash ✔️
* Manual scrolling doesn't crash ✔️
* ^G / the audible bell doesn't crash ✔️
This commit is contained in:
Leonard Hecker 2021-07-06 23:59:44 +02:00 committed by GitHub
parent 59239e3b07
commit 83c6bce73d
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2 changed files with 65 additions and 75 deletions

View file

@ -48,9 +48,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{ {
TermControl::TermControl(IControlSettings settings, TermControl::TermControl(IControlSettings settings,
TerminalConnection::ITerminalConnection connection) : TerminalConnection::ITerminalConnection connection) :
_initializedTerminal{ false },
_settings{ settings }, _settings{ settings },
_closing{ false },
_isInternalScrollBarUpdate{ false }, _isInternalScrollBarUpdate{ false },
_autoScrollVelocity{ 0 }, _autoScrollVelocity{ 0 },
_autoScrollingPointerPoint{ std::nullopt }, _autoScrollingPointerPoint{ std::nullopt },
@ -110,11 +108,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Core eventually won't have access to. When we get to // Core eventually won't have access to. When we get to
// https://github.com/microsoft/terminal/projects/5#card-50760282 // https://github.com/microsoft/terminal/projects/5#card-50760282
// then we'll move the applicable ones. // then we'll move the applicable ones.
//
// These four throttled functions are triggered by terminal output and interact with the UI.
// Since Close() is the point after which we are removed from the UI, but before the
// destructor has run, we MUST check control->_IsClosing() before actually doing anything.
_tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>( _tsfTryRedrawCanvas = std::make_shared<ThrottledFuncTrailing<>>(
Dispatcher(), Dispatcher(),
TsfRedrawInterval, TsfRedrawInterval,
[weakThis = get_weak()]() { [weakThis = get_weak()]() {
if (auto control{ weakThis.get() }) if (auto control{ weakThis.get() }; !control->_IsClosing())
{ {
control->TSFInputControl().TryRedrawCanvas(); control->TSFInputControl().TryRedrawCanvas();
} }
@ -124,7 +126,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Dispatcher(), Dispatcher(),
UpdatePatternLocationsInterval, UpdatePatternLocationsInterval,
[weakThis = get_weak()]() { [weakThis = get_weak()]() {
if (auto control{ weakThis.get() }) if (auto control{ weakThis.get() }; !control->_IsClosing())
{ {
control->_core->UpdatePatternLocations(); control->_core->UpdatePatternLocations();
} }
@ -134,7 +136,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Dispatcher(), Dispatcher(),
TerminalWarningBellInterval, TerminalWarningBellInterval,
[weakThis = get_weak()]() { [weakThis = get_weak()]() {
if (auto control{ weakThis.get() }) if (auto control{ weakThis.get() }; !control->_IsClosing())
{ {
control->_WarningBellHandlers(*control, nullptr); control->_WarningBellHandlers(*control, nullptr);
} }
@ -144,14 +146,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Dispatcher(), Dispatcher(),
ScrollBarUpdateInterval, ScrollBarUpdateInterval,
[weakThis = get_weak()](const auto& update) { [weakThis = get_weak()](const auto& update) {
if (auto control{ weakThis.get() }) if (auto control{ weakThis.get() }; !control->_IsClosing())
{ {
control->_isInternalScrollBarUpdate = true; control->_isInternalScrollBarUpdate = true;
auto scrollBar = control->ScrollBar(); auto scrollBar = control->ScrollBar();
if (update.newValue.has_value()) if (update.newValue)
{ {
scrollBar.Value(update.newValue.value()); scrollBar.Value(*update.newValue);
} }
scrollBar.Maximum(update.newMaximum); scrollBar.Maximum(update.newMaximum);
scrollBar.Minimum(update.newMinimum); scrollBar.Minimum(update.newMinimum);
@ -205,7 +207,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::SearchMatch(const bool goForward) void TermControl::SearchMatch(const bool goForward)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -296,7 +298,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - newSettings: the new settings to set // - newSettings: the new settings to set
void TermControl::_UpdateSettingsFromUIThread(IControlSettings newSettings) void TermControl::_UpdateSettingsFromUIThread(IControlSettings newSettings)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -314,7 +316,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - newAppearance: the new appearance to set // - newAppearance: the new appearance to set
void TermControl::_UpdateAppearanceFromUIThread(IControlAppearance newAppearance) void TermControl::_UpdateAppearanceFromUIThread(IControlAppearance newAppearance)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -527,7 +529,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer() Windows::UI::Xaml::Automation::Peers::AutomationPeer TermControl::OnCreateAutomationPeer()
try try
{ {
if (_initializedTerminal && !_closing) // only set up the automation peer if we're ready to go live if (_initializedTerminal && !_IsClosing()) // only set up the automation peer if we're ready to go live
{ {
// create a custom automation peer with this code pattern: // create a custom automation peer with this code pattern:
// (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers) // (https://docs.microsoft.com/en-us/windows/uwp/design/accessibility/custom-automation-peers)
@ -747,7 +749,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_CharacterHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/, void TermControl::_CharacterHandler(winrt::Windows::Foundation::IInspectable const& /*sender*/,
Input::CharacterReceivedRoutedEventArgs const& e) Input::CharacterReceivedRoutedEventArgs const& e)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -845,7 +847,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// win32-input-mode, then we'll send all these keystrokes to the // win32-input-mode, then we'll send all these keystrokes to the
// terminal - it's smart enough to ignore the keys it doesn't care // terminal - it's smart enough to ignore the keys it doesn't care
// about. // about.
if (_closing || if (_IsClosing() ||
e.OriginalKey() == VirtualKey::LeftWindows || e.OriginalKey() == VirtualKey::LeftWindows ||
e.OriginalKey() == VirtualKey::RightWindows) e.OriginalKey() == VirtualKey::RightWindows)
@ -997,12 +999,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
keyDown) : keyDown) :
true; true;
if (_cursorTimer.has_value()) if (_cursorTimer)
{ {
// Manually show the cursor when a key is pressed. Restarting // Manually show the cursor when a key is pressed. Restarting
// the timer prevents flickering. // the timer prevents flickering.
_core->CursorOn(true); _core->CursorOn(true);
_cursorTimer.value().Start(); _cursorTimer->Start();
} }
return handled; return handled;
@ -1027,7 +1029,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_PointerPressedHandler(Windows::Foundation::IInspectable const& sender, void TermControl::_PointerPressedHandler(Windows::Foundation::IInspectable const& sender,
Input::PointerRoutedEventArgs const& args) Input::PointerRoutedEventArgs const& args)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -1078,7 +1080,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_PointerMovedHandler(Windows::Foundation::IInspectable const& /*sender*/, void TermControl::_PointerMovedHandler(Windows::Foundation::IInspectable const& /*sender*/,
Input::PointerRoutedEventArgs const& args) Input::PointerRoutedEventArgs const& args)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -1151,7 +1153,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_PointerReleasedHandler(Windows::Foundation::IInspectable const& sender, void TermControl::_PointerReleasedHandler(Windows::Foundation::IInspectable const& sender,
Input::PointerRoutedEventArgs const& args) Input::PointerRoutedEventArgs const& args)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -1193,7 +1195,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/, void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/,
Input::PointerRoutedEventArgs const& args) Input::PointerRoutedEventArgs const& args)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -1295,7 +1297,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_ScrollbarChangeHandler(Windows::Foundation::IInspectable const& /*sender*/, void TermControl::_ScrollbarChangeHandler(Windows::Foundation::IInspectable const& /*sender*/,
Controls::Primitives::RangeBaseValueChangedEventArgs const& args) Controls::Primitives::RangeBaseValueChangedEventArgs const& args)
{ {
if (_isInternalScrollBarUpdate || _closing) if (_isInternalScrollBarUpdate || _IsClosing())
{ {
// The update comes from ourselves, more specifically from the // The update comes from ourselves, more specifically from the
// terminal. So we don't have to update the terminal because it // terminal. So we don't have to update the terminal because it
@ -1361,15 +1363,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_TryStartAutoScroll(Windows::UI::Input::PointerPoint const& pointerPoint, const double scrollVelocity) void TermControl::_TryStartAutoScroll(Windows::UI::Input::PointerPoint const& pointerPoint, const double scrollVelocity)
{ {
// Allow only one pointer at the time // Allow only one pointer at the time
if (!_autoScrollingPointerPoint.has_value() || if (!_autoScrollingPointerPoint ||
_autoScrollingPointerPoint.value().PointerId() == pointerPoint.PointerId()) _autoScrollingPointerPoint->PointerId() == pointerPoint.PointerId())
{ {
_autoScrollingPointerPoint = pointerPoint; _autoScrollingPointerPoint = pointerPoint;
_autoScrollVelocity = scrollVelocity; _autoScrollVelocity = scrollVelocity;
// If this is first time the auto scroll update is about to be called, // If this is first time the auto scroll update is about to be called,
// kick-start it by initializing its time delta as if it started now // kick-start it by initializing its time delta as if it started now
if (!_lastAutoScrollUpdateTime.has_value()) if (!_lastAutoScrollUpdateTime)
{ {
_lastAutoScrollUpdateTime = std::chrono::high_resolution_clock::now(); _lastAutoScrollUpdateTime = std::chrono::high_resolution_clock::now();
} }
@ -1388,8 +1390,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - pointerId: id of pointer for which to stop auto scroll // - pointerId: id of pointer for which to stop auto scroll
void TermControl::_TryStopAutoScroll(const uint32_t pointerId) void TermControl::_TryStopAutoScroll(const uint32_t pointerId)
{ {
if (_autoScrollingPointerPoint.has_value() && if (_autoScrollingPointerPoint &&
pointerId == _autoScrollingPointerPoint.value().PointerId()) pointerId == _autoScrollingPointerPoint->PointerId())
{ {
_autoScrollingPointerPoint = std::nullopt; _autoScrollingPointerPoint = std::nullopt;
_autoScrollVelocity = 0; _autoScrollVelocity = 0;
@ -1415,15 +1417,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{ {
const auto timeNow = std::chrono::high_resolution_clock::now(); const auto timeNow = std::chrono::high_resolution_clock::now();
if (_lastAutoScrollUpdateTime.has_value()) if (_lastAutoScrollUpdateTime)
{ {
static constexpr double microSecPerSec = 1000000.0; static constexpr double microSecPerSec = 1000000.0;
const double deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(timeNow - _lastAutoScrollUpdateTime.value()).count() / microSecPerSec; const double deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(timeNow - *_lastAutoScrollUpdateTime).count() / microSecPerSec;
ScrollBar().Value(ScrollBar().Value() + _autoScrollVelocity * deltaTime); ScrollBar().Value(ScrollBar().Value() + _autoScrollVelocity * deltaTime);
if (_autoScrollingPointerPoint.has_value()) if (_autoScrollingPointerPoint)
{ {
_SetEndSelectionPointAtCursor(_autoScrollingPointerPoint.value().Position()); _SetEndSelectionPointAtCursor(_autoScrollingPointerPoint->Position());
} }
} }
@ -1439,7 +1441,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */, void TermControl::_GotFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */) RoutedEventArgs const& /* args */)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -1468,16 +1470,16 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TSFInputControl().NotifyFocusEnter(); TSFInputControl().NotifyFocusEnter();
} }
if (_cursorTimer.has_value()) if (_cursorTimer)
{ {
// When the terminal focuses, show the cursor immediately // When the terminal focuses, show the cursor immediately
_core->CursorOn(true); _core->CursorOn(true);
_cursorTimer.value().Start(); _cursorTimer->Start();
} }
if (_blinkTimer.has_value()) if (_blinkTimer)
{ {
_blinkTimer.value().Start(); _blinkTimer->Start();
} }
_interactivity->GainFocus(); _interactivity->GainFocus();
@ -1498,7 +1500,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */, void TermControl::_LostFocusHandler(Windows::Foundation::IInspectable const& /* sender */,
RoutedEventArgs const& /* args */) RoutedEventArgs const& /* args */)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -1517,15 +1519,15 @@ namespace winrt::Microsoft::Terminal::Control::implementation
TSFInputControl().NotifyFocusLeave(); TSFInputControl().NotifyFocusLeave();
} }
if (_cursorTimer.has_value()) if (_cursorTimer)
{ {
_cursorTimer.value().Stop(); _cursorTimer->Stop();
_core->CursorOn(false); _core->CursorOn(false);
} }
if (_blinkTimer.has_value()) if (_blinkTimer)
{ {
_blinkTimer.value().Stop(); _blinkTimer->Stop();
} }
// Check if there is an unfocused config we should set the appearance to // Check if there is an unfocused config we should set the appearance to
@ -1544,7 +1546,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_SwapChainSizeChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/, void TermControl::_SwapChainSizeChanged(winrt::Windows::Foundation::IInspectable const& /*sender*/,
SizeChangedEventArgs const& e) SizeChangedEventArgs const& e)
{ {
if (!_initializedTerminal || _closing) if (!_initializedTerminal || _IsClosing())
{ {
return; return;
} }
@ -1596,7 +1598,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_CursorTimerTick(Windows::Foundation::IInspectable const& /* sender */, void TermControl::_CursorTimerTick(Windows::Foundation::IInspectable const& /* sender */,
Windows::Foundation::IInspectable const& /* e */) Windows::Foundation::IInspectable const& /* e */)
{ {
if (!_closing) if (!_IsClosing())
{ {
_core->BlinkCursor(); _core->BlinkCursor();
} }
@ -1610,7 +1612,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_BlinkTimerTick(Windows::Foundation::IInspectable const& /* sender */, void TermControl::_BlinkTimerTick(Windows::Foundation::IInspectable const& /* sender */,
Windows::Foundation::IInspectable const& /* e */) Windows::Foundation::IInspectable const& /* e */)
{ {
if (!_closing) if (!_IsClosing())
{ {
_core->BlinkAttributeTick(); _core->BlinkAttributeTick();
} }
@ -1638,13 +1640,6 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_ScrollPositionChanged(const IInspectable& /*sender*/, void TermControl::_ScrollPositionChanged(const IInspectable& /*sender*/,
const Control::ScrollPositionChangedArgs& args) const Control::ScrollPositionChangedArgs& args)
{ {
// Since this callback fires from non-UI thread, we might be already
// closed/closing.
if (_closing.load())
{
return;
}
ScrollBarUpdate update; ScrollBarUpdate update;
const auto hiddenContent = args.BufferSize() - args.ViewHeight(); const auto hiddenContent = args.BufferSize() - args.ViewHeight();
update.newMaximum = hiddenContent; update.newMaximum = hiddenContent;
@ -1664,10 +1659,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_CursorPositionChanged(const IInspectable& /*sender*/, void TermControl::_CursorPositionChanged(const IInspectable& /*sender*/,
const IInspectable& /*args*/) const IInspectable& /*args*/)
{ {
if (_tsfTryRedrawCanvas) _tsfTryRedrawCanvas->Run();
{
_tsfTryRedrawCanvas->Run();
}
} }
hstring TermControl::Title() hstring TermControl::Title()
@ -1700,7 +1692,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// if we should defer which formats are copied to the global setting // if we should defer which formats are copied to the global setting
bool TermControl::CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats) bool TermControl::CopySelectionToClipboard(bool singleLine, const Windows::Foundation::IReference<CopyFormat>& formats)
{ {
if (_closing) if (_IsClosing())
{ {
return false; return false;
} }
@ -1717,21 +1709,12 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::Close() void TermControl::Close()
{ {
if (!_closing.exchange(true)) if (!_IsClosing())
{ {
_closing = true;
_core->ReceivedOutput(_coreOutputEventToken); _core->ReceivedOutput(_coreOutputEventToken);
_RestorePointerCursorHandlers(*this, nullptr); _RestorePointerCursorHandlers(*this, nullptr);
// These four throttled functions are triggered by terminal output and interact with the UI.
// Since Close() is the point after which we are removed from the UI, but before the destructor
// has run, we should disconnect them *right now*. If we don't, they may fire between the
// throttle delay (from the final output) and the dtor.
_tsfTryRedrawCanvas.reset();
_updatePatternLocations.reset();
_updateScrollBar.reset();
_playWarningBell.reset();
// Disconnect the TSF input control so it doesn't receive EditContext events. // Disconnect the TSF input control so it doesn't receive EditContext events.
TSFInputControl().Close(); TSFInputControl().Close();
_autoScrollTimer.Stop(); _autoScrollTimer.Stop();
@ -2097,7 +2080,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// - <none> // - <none>
void TermControl::_CompositionCompleted(winrt::hstring text) void TermControl::_CompositionCompleted(winrt::hstring text)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -2174,7 +2157,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::fire_and_forget TermControl::_DragDropHandler(Windows::Foundation::IInspectable /*sender*/, winrt::fire_and_forget TermControl::_DragDropHandler(Windows::Foundation::IInspectable /*sender*/,
DragEventArgs e) DragEventArgs e)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -2261,7 +2244,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void TermControl::_DragOverHandler(Windows::Foundation::IInspectable const& /*sender*/, void TermControl::_DragOverHandler(Windows::Foundation::IInspectable const& /*sender*/,
DragEventArgs const& e) DragEventArgs const& e)
{ {
if (_closing) if (_IsClosing())
{ {
return; return;
} }
@ -2422,7 +2405,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
// Stop the timer and switch off the light // Stop the timer and switch off the light
_bellLightTimer.Stop(); _bellLightTimer.Stop();
if (!_closing) if (!_IsClosing())
{ {
VisualBellLight::SetIsTarget(RootGrid(), false); VisualBellLight::SetIsTarget(RootGrid(), false);
} }
@ -2465,7 +2448,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
if (auto self{ weakThis.get() }) if (auto self{ weakThis.get() })
{ {
auto lastHoveredCell = _core->GetHoveredCell(); auto lastHoveredCell = _core->GetHoveredCell();
if (lastHoveredCell.has_value()) if (lastHoveredCell)
{ {
const auto uriText = _core->GetHoveredUriText(); const auto uriText = _core->GetHoveredUriText();
if (!uriText.empty()) if (!uriText.empty())

View file

@ -149,9 +149,9 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::com_ptr<SearchBoxControl> _searchBox; winrt::com_ptr<SearchBoxControl> _searchBox;
IControlSettings _settings; IControlSettings _settings;
std::atomic<bool> _closing; bool _closing{ false };
bool _focused; bool _focused{ false };
bool _initializedTerminal; bool _initializedTerminal{ false };
std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas; std::shared_ptr<ThrottledFuncTrailing<>> _tsfTryRedrawCanvas;
std::shared_ptr<ThrottledFuncTrailing<>> _updatePatternLocations; std::shared_ptr<ThrottledFuncTrailing<>> _updatePatternLocations;
@ -183,6 +183,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker; winrt::Windows::UI::Xaml::Controls::SwapChainPanel::LayoutUpdated_revoker _layoutUpdatedRevoker;
inline bool _IsClosing() const noexcept
{
// _closing isn't atomic and may only be accessed from the main thread.
assert(Dispatcher().HasThreadAccess());
return _closing;
}
void _UpdateSettingsFromUIThread(IControlSettings newSettings); void _UpdateSettingsFromUIThread(IControlSettings newSettings);
void _UpdateAppearanceFromUIThread(IControlAppearance newAppearance); void _UpdateAppearanceFromUIThread(IControlAppearance newAppearance);
void _ApplyUISettings(const IControlSettings&); void _ApplyUISettings(const IControlSettings&);