未验证 提交 cf8bd1fb 编写于 作者: S Sam Bent 提交者: GitHub

Merge pull request #3567 from SamBent/HostVisualThreading

HostVisual threading
...@@ -125,9 +125,16 @@ internal override void FreeContent(DUCE.Channel channel) ...@@ -125,9 +125,16 @@ internal override void FreeContent(DUCE.Channel channel)
using (CompositionEngineLock.Acquire()) using (CompositionEngineLock.Acquire())
{ {
DisconnectHostedVisual( // if there's a pending disconnect, do it now preemptively;
channel, // otherwise do the disconnect the normal way.
/* removeChannelFromCollection */ true); // This ensures we do the disconnect before calling base,
// as required.
if (!DoPendingDisconnect(channel))
{
DisconnectHostedVisual(
channel,
/* removeChannelFromCollection */ true);
}
} }
base.FreeContent(channel); base.FreeContent(channel);
...@@ -252,7 +259,7 @@ private void EnsureHostedVisualConnected(DUCE.Channel channel) ...@@ -252,7 +259,7 @@ private void EnsureHostedVisualConnected(DUCE.Channel channel)
// //
if (!(channel.IsSynchronous) if (!(channel.IsSynchronous)
&& _target != null && _target != null
&& !_connectedChannels.Contains(channel)) && !_connectedChannels.ContainsKey(channel))
{ {
Debug.Assert(IsOnChannel(channel)); Debug.Assert(IsOnChannel(channel));
...@@ -323,7 +330,15 @@ private void EnsureHostedVisualConnected(DUCE.Channel channel) ...@@ -323,7 +330,15 @@ private void EnsureHostedVisualConnected(DUCE.Channel 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 // Indicate that that content composition root has been
...@@ -364,10 +379,11 @@ private void EnsureHostedVisualConnected(DUCE.Channel channel) ...@@ -364,10 +379,11 @@ private void EnsureHostedVisualConnected(DUCE.Channel channel)
/// </summary> /// </summary>
private void DisconnectHostedVisualOnAllChannels() private void DisconnectHostedVisualOnAllChannels()
{ {
foreach (DUCE.Channel channel in _connectedChannels) IDictionaryEnumerator ide = _connectedChannels.GetEnumerator() as IDictionaryEnumerator;
while (ide.MoveNext())
{ {
DisconnectHostedVisual( DisconnectHostedVisual(
channel, (DUCE.Channel)ide.Key,
/* removeChannelFromCollection */ false); /* removeChannelFromCollection */ false);
} }
...@@ -382,23 +398,41 @@ private void DisconnectHostedVisualOnAllChannels() ...@@ -382,23 +398,41 @@ private void DisconnectHostedVisualOnAllChannels()
DUCE.Channel channel, DUCE.Channel channel,
bool removeChannelFromCollection) bool removeChannelFromCollection)
{ {
if (_target != null && _connectedChannels.Contains(channel)) Dispatcher channelDispatcher;
if (_target != null && _connectedChannels.TryGetValue(channel, out channelDispatcher))
{ {
DUCE.CompositionNode.RemoveChild( // Adding commands to a channel is not thread-safe,
_proxy.GetHandle(channel), // we must do the actual work on the same dispatcher thread
_target._contentRoot.GetHandle(channel), // where the connection happened.
channel if (channelDispatcher != null && channelDispatcher.CheckAccess())
); {
Disconnect(channel,
// channelDispatcher,
// Release the targets handle. If we had duplicated the handle, _proxy.GetHandle(channel),
// then this removes the duplicated handle, otherwise just decrease _target._contentRoot.GetHandle(channel),
// the ref count for VisualTarget. _target._contentRoot);
// }
else
_target._contentRoot.ReleaseOnChannel(channel); {
// marshal to the right thread
SetFlags(channel, false, VisualProxyFlags.IsContentNodeConnected); if (channelDispatcher != null)
{
DispatcherOperation op = channelDispatcher.BeginInvoke(
DispatcherPriority.Normal,
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) if (removeChannelFromCollection)
{ {
...@@ -407,6 +441,101 @@ private void DisconnectHostedVisualOnAllChannels() ...@@ -407,6 +441,101 @@ private void DisconnectHostedVisualOnAllChannels()
} }
} }
/// <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)
{
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> /// <summary>
/// Invalidate this visual. /// Invalidate this visual.
...@@ -443,7 +572,49 @@ private void Invalidate() ...@@ -443,7 +572,49 @@ private void Invalidate()
/// <remarks> /// <remarks>
/// This field is free-threaded and should be accessed from under a lock. /// This field is free-threaded and should be accessed from under a lock.
/// </remarks> /// </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 #endregion Private Fields
} }
......
...@@ -365,6 +365,15 @@ internal struct ChannelSet ...@@ -365,6 +365,15 @@ internal struct ChannelSet
/// channel. /// channel.
/// </summary> /// </summary>
internal sealed partial class Channel internal sealed partial class Channel
#if ENFORCE_CHANNEL_THREAD_ACCESS
: System.Windows.Threading.DispatcherObject
// "Producer" operations - adding commands et al. - should only be done
// on the thread that created the channel. These operations are on the
// hot path, so we don't add the cost of enforcement. To detect
// violations (which can lead to render-thread failures that
// are very difficult to diagnose), build
// PresentationCore with ENFORCE_CHANNEL_THREAD_ACCESS defined.
#endif
{ {
/// <summary> /// <summary>
/// Primary channel. /// Primary channel.
...@@ -768,6 +777,10 @@ internal bool IsOutOfBandChannel ...@@ -768,6 +777,10 @@ internal bool IsOutOfBandChannel
int cSize, int cSize,
bool sendInSeparateBatch) bool sendInSeparateBatch)
{ {
#if ENFORCE_CHANNEL_THREAD_ACCESS
VerifyAccess();
#endif
checked checked
{ {
Invariant.Assert(pCommandData != (byte*)0 && cSize > 0); Invariant.Assert(pCommandData != (byte*)0 && cSize > 0);
...@@ -808,6 +821,10 @@ internal bool IsOutOfBandChannel ...@@ -808,6 +821,10 @@ internal bool IsOutOfBandChannel
int cbSize, int cbSize,
int cbExtra) int cbExtra)
{ {
#if ENFORCE_CHANNEL_THREAD_ACCESS
VerifyAccess();
#endif
checked checked
{ {
Invariant.Assert(cbSize > 0); Invariant.Assert(cbSize > 0);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册