-
Notifications
You must be signed in to change notification settings - Fork 1.2k
HostVisual threading #3567
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
HostVisual threading #3567
Changes from 2 commits
Commits
Show all changes
3 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -125,9 +125,16 @@ internal override void FreeContent(DUCE.Channel channel) | |
|
||
using (CompositionEngineLock.Acquire()) | ||
{ | ||
DisconnectHostedVisual( | ||
channel, | ||
/* removeChannelFromCollection */ true); | ||
// if there's a pending disconnect, do it now preemptively; | ||
// otherwise do the disconnect the normal way. | ||
// This ensures we do the disconnect before calling base, | ||
// as required. | ||
if (!DoPendingDisconnect(channel)) | ||
{ | ||
DisconnectHostedVisual( | ||
channel, | ||
/* removeChannelFromCollection */ true); | ||
} | ||
} | ||
|
||
base.FreeContent(channel); | ||
|
@@ -252,7 +259,7 @@ private void EnsureHostedVisualConnected(DUCE.Channel channel) | |
// | ||
if (!(channel.IsSynchronous) | ||
&& _target != null | ||
&& !_connectedChannels.Contains(channel)) | ||
&& !_connectedChannels.ContainsKey(channel)) | ||
{ | ||
Debug.Assert(IsOnChannel(channel)); | ||
|
||
|
@@ -323,7 +330,15 @@ private void EnsureHostedVisualConnected(DUCE.Channel channel) | |
channel); | ||
} | ||
|
||
_connectedChannels.Add(channel); | ||
// remember what channel we connected to, and which thread | ||
// did the connection, so that we can disconnect on the | ||
// same thread. Earlier comments imply this is the HostVisual's | ||
// dispatcher thread, which we assert here. Even if it's not, | ||
// the code downstream should work, or at least not crash | ||
// (even if channelDispatcher is set to null). | ||
Dispatcher channelDispatcher = Dispatcher.FromThread(Thread.CurrentThread); | ||
Debug.Assert(channelDispatcher == this.Dispatcher, "HostVisual connecting on a second thread"); | ||
_connectedChannels.Add(channel, channelDispatcher); | ||
|
||
// | ||
// Indicate that that content composition root has been | ||
|
@@ -364,10 +379,11 @@ private void EnsureHostedVisualConnected(DUCE.Channel channel) | |
/// </summary> | ||
private void DisconnectHostedVisualOnAllChannels() | ||
{ | ||
foreach (DUCE.Channel channel in _connectedChannels) | ||
IDictionaryEnumerator ide = _connectedChannels.GetEnumerator() as IDictionaryEnumerator; | ||
while (ide.MoveNext()) | ||
{ | ||
DisconnectHostedVisual( | ||
channel, | ||
(DUCE.Channel)ide.Key, | ||
/* removeChannelFromCollection */ false); | ||
} | ||
|
||
|
@@ -382,23 +398,42 @@ private void DisconnectHostedVisual( | |
DUCE.Channel channel, | ||
bool removeChannelFromCollection) | ||
{ | ||
if (_target != null && _connectedChannels.Contains(channel)) | ||
Dispatcher channelDispatcher; | ||
if (_target != null && _connectedChannels.TryGetValue(channel, out channelDispatcher)) | ||
{ | ||
DUCE.CompositionNode.RemoveChild( | ||
_proxy.GetHandle(channel), | ||
_target._contentRoot.GetHandle(channel), | ||
channel | ||
); | ||
|
||
// | ||
// Release the targets handle. If we had duplicated the handle, | ||
// then this removes the duplicated handle, otherwise just decrease | ||
// the ref count for VisualTarget. | ||
// | ||
|
||
_target._contentRoot.ReleaseOnChannel(channel); | ||
|
||
SetFlags(channel, false, VisualProxyFlags.IsContentNodeConnected); | ||
// Adding commands to a channel is not thread-safe, | ||
// we must do the actual work on the same dispatcher thread | ||
// where the connection happened. | ||
if (CoreAppContextSwitches.HostVisualDisconnectsOnWrongThread || | ||
(channelDispatcher != null && channelDispatcher.CheckAccess())) | ||
{ | ||
Disconnect(channel, | ||
channelDispatcher, | ||
_proxy.GetHandle(channel), | ||
_target._contentRoot.GetHandle(channel), | ||
_target._contentRoot); | ||
} | ||
else | ||
{ | ||
// marshal to the right thread | ||
if (channelDispatcher != null) | ||
{ | ||
DispatcherOperation op = channelDispatcher.BeginInvoke( | ||
DispatcherPriority.Normal, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Should we use Render priority? I think it's a rendering task. |
||
new DispatcherOperationCallback(DoDisconnectHostedVisual), | ||
channel); | ||
|
||
_disconnectData = new DisconnectData( | ||
op: op, | ||
channel: channel, | ||
dispatcher: channelDispatcher, | ||
hostVisual: this, | ||
hostHandle: _proxy.GetHandle(channel), | ||
targetHandle: _target._contentRoot.GetHandle(channel), | ||
contentRoot: _target._contentRoot, | ||
next: _disconnectData); | ||
} | ||
} | ||
|
||
if (removeChannelFromCollection) | ||
{ | ||
|
@@ -407,6 +442,104 @@ private void DisconnectHostedVisual( | |
} | ||
} | ||
|
||
/// <summary> | ||
/// Callback to disconnect on the right thread | ||
/// </summary> | ||
private object DoDisconnectHostedVisual(object arg) | ||
{ | ||
using (CompositionEngineLock.Acquire()) | ||
{ | ||
DoPendingDisconnect((DUCE.Channel)arg); | ||
} | ||
|
||
return null; | ||
} | ||
|
||
/// <summary> | ||
/// Perform a pending disconnect for the given channel. | ||
/// This method should be called under the CompositionEngineLock, | ||
/// on the thread that owns the channel. It can be called either | ||
/// from the dispatcher callback DoDisconnectHostedVisual or | ||
/// from FreeContent, whichever happens to occur first. | ||
/// </summary> | ||
/// <returns> | ||
/// True if a matching request was found and processed. False if not. | ||
/// </returns> | ||
private bool DoPendingDisconnect(DUCE.Channel channel) | ||
{ | ||
DisconnectData disconnectData = _disconnectData; | ||
DisconnectData previous = null; | ||
|
||
// search the list for an entry matching the given channel | ||
while (disconnectData != null && (disconnectData.HostVisual != this || disconnectData.Channel != channel)) | ||
{ | ||
previous = disconnectData; | ||
disconnectData = disconnectData.Next; | ||
} | ||
|
||
// if no match found, do nothing | ||
if (disconnectData == null) | ||
{ | ||
return false; | ||
} | ||
|
||
// remove the matching entry from the list | ||
if (previous == null) | ||
{ | ||
_disconnectData = disconnectData.Next; | ||
} | ||
else | ||
{ | ||
previous.Next = disconnectData.Next; | ||
} | ||
|
||
// cancel the dispatcher callback, (if we're already in it, | ||
// this call is a no-op) | ||
disconnectData.DispatcherOperation.Abort(); | ||
|
||
// do the actual disconnect | ||
Disconnect(disconnectData.Channel, | ||
disconnectData.ChannelDispatcher, | ||
disconnectData.HostHandle, | ||
disconnectData.TargetHandle, | ||
disconnectData.ContentRoot); | ||
|
||
return true; | ||
} | ||
|
||
/// <summary> | ||
/// Do the actual work to disconnect the VisualTarget. | ||
/// This is called (on the channel's thread) either from | ||
/// DisconnectHostedVisual or from DoPendingDisconnect, | ||
/// depending on which thread the request arrived on. | ||
/// </summary> | ||
private void Disconnect(DUCE.Channel channel, | ||
Dispatcher channelDispatcher, | ||
DUCE.ResourceHandle hostHandle, | ||
DUCE.ResourceHandle targetHandle, | ||
DUCE.MultiChannelResource contentRoot) | ||
{ | ||
if (!CoreAppContextSwitches.HostVisualDisconnectsOnWrongThread) | ||
{ | ||
channelDispatcher.VerifyAccess(); | ||
} | ||
|
||
DUCE.CompositionNode.RemoveChild( | ||
hostHandle, | ||
targetHandle, | ||
channel | ||
); | ||
|
||
// | ||
// Release the targets handle. If we had duplicated the handle, | ||
// then this removes the duplicated handle, otherwise just decrease | ||
// the ref count for VisualTarget. | ||
// | ||
|
||
contentRoot.ReleaseOnChannel(channel); | ||
|
||
SetFlags(channel, false, VisualProxyFlags.IsContentNodeConnected); | ||
} | ||
|
||
/// <summary> | ||
/// Invalidate this visual. | ||
|
@@ -443,7 +576,49 @@ private void Invalidate() | |
/// <remarks> | ||
/// This field is free-threaded and should be accessed from under a lock. | ||
/// </remarks> | ||
private List<DUCE.Channel> _connectedChannels = new List<DUCE.Channel>(); | ||
private Dictionary<DUCE.Channel, Dispatcher> _connectedChannels = new Dictionary<DUCE.Channel, Dispatcher>(); | ||
|
||
/// <summary> | ||
/// Data needed to disconnect the visual target. | ||
/// </summary> | ||
/// <remarks> | ||
/// This field is free-threaded and should be accessed from under a lock. | ||
/// It's the head of a singly-linked list of pending disconnect requests, | ||
/// each identified by the channel and HostVisual. In practice, the list | ||
/// is either empty or has only one entry. | ||
/// </remarks> | ||
private static DisconnectData _disconnectData; | ||
|
||
private class DisconnectData | ||
{ | ||
public DispatcherOperation DispatcherOperation { get; private set; } | ||
public DUCE.Channel Channel { get; private set; } | ||
public Dispatcher ChannelDispatcher { get; private set; } | ||
public HostVisual HostVisual { get; private set; } | ||
public DUCE.ResourceHandle HostHandle { get; private set; } | ||
public DUCE.ResourceHandle TargetHandle { get; private set; } | ||
public DUCE.MultiChannelResource ContentRoot { get; private set; } | ||
public DisconnectData Next { get; set; } | ||
|
||
public DisconnectData(DispatcherOperation op, | ||
DUCE.Channel channel, | ||
Dispatcher dispatcher, | ||
HostVisual hostVisual, | ||
DUCE.ResourceHandle hostHandle, | ||
DUCE.ResourceHandle targetHandle, | ||
DUCE.MultiChannelResource contentRoot, | ||
DisconnectData next) | ||
{ | ||
DispatcherOperation = op; | ||
Channel = channel; | ||
ChannelDispatcher = dispatcher; | ||
HostVisual = hostVisual; | ||
HostHandle = hostHandle; | ||
TargetHandle = targetHandle; | ||
ContentRoot = contentRoot; | ||
Next = next; | ||
} | ||
} | ||
|
||
#endregion Private Fields | ||
} | ||
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.