| File: System\Xaml\XamlBackgroundReader.cs | Web Access |
| Project: src\wpf\src\Microsoft.DotNet.Wpf\src\System.Xaml\System.Xaml.csproj (System.Xaml) |
// Licensed to the .NET Foundation under one or more agreements. // The .NET Foundation licenses this file to you under the MIT license. #nullable disable using System.Threading; namespace System.Xaml { // XamlNode Double Buffering object that multi-threaded access // ONE reader and ONE writer. // This is the bridge used to Parse on one thread and Build on a second. public class XamlBackgroundReader : XamlReader, IXamlLineInfo { private EventWaitHandle _providerFullEvent; private EventWaitHandle _dataReceivedEvent; private XamlNode[] _incoming; private int _inIdx; private XamlNode[] _outgoing; private int _outIdx; private int _outValid; private XamlNode _currentNode; private XamlReader _wrappedReader; private XamlReader _internalReader; private XamlWriter _writer; private bool _wrappedReaderHasLineInfo; private int _lineNumber; private int _linePosition; private Thread _thread; private Exception _caughtException; public XamlBackgroundReader(XamlReader wrappedReader) { ArgumentNullException.ThrowIfNull(wrappedReader); Initialize(wrappedReader, 64); } private void Initialize(XamlReader wrappedReader, int bufferSize) { _providerFullEvent = new EventWaitHandle(false, EventResetMode.AutoReset); _dataReceivedEvent = new EventWaitHandle(false, EventResetMode.AutoReset); _incoming = new XamlNode[bufferSize]; _outgoing = new XamlNode[bufferSize]; _wrappedReader = wrappedReader; _wrappedReaderHasLineInfo = ((IXamlLineInfo)_wrappedReader).HasLineInfo; var xamlNodeAddDelegate = new XamlNodeAddDelegate(Add); XamlLineInfoAddDelegate lineInfoAddDelegate = null; if (_wrappedReaderHasLineInfo) { lineInfoAddDelegate = new XamlLineInfoAddDelegate(AddLineInfo); } _writer = new WriterDelegate(xamlNodeAddDelegate, lineInfoAddDelegate, _wrappedReader.SchemaContext); XamlNodeNextDelegate xamlNodeNextDelegate; if (_wrappedReaderHasLineInfo) { xamlNodeNextDelegate = new XamlNodeNextDelegate(Next_ProcessLineInfo); } else { xamlNodeNextDelegate = new XamlNodeNextDelegate(Next); } _internalReader = new ReaderDelegate(_wrappedReader.SchemaContext, xamlNodeNextDelegate, _wrappedReaderHasLineInfo); // Standin so it won't start null _currentNode = new XamlNode(XamlNode.InternalNodeType.StartOfStream); } public void StartThread() { StartThread("XAML reader thread"); } public void StartThread(string threadName) { if (_thread is not null) { throw new InvalidOperationException(SR.ThreadAlreadyStarted); } ParameterizedThreadStart start = new ParameterizedThreadStart(XamlReaderThreadStart); _thread = new Thread(start) { Name = threadName }; _thread.Start(); } // The "ThreadStart" function private void XamlReaderThreadStart(object none) { try { InterruptableTransform(_wrappedReader, _writer, true); } catch (Exception ex) { _writer.Close(); _caughtException = ex; } } internal bool IncomingFull { get { return _inIdx >= _incoming.Length; } } internal bool OutgoingEmpty { get { return _outIdx >= _outValid; } } private void SwapBuffers() { XamlNode[] tmp = _incoming; _incoming = _outgoing; _outgoing = tmp; _outIdx = 0; _outValid = _inIdx; // in the EOF case the buffer can be short. _inIdx = 0; } private void AddToBuffer(XamlNode node) { _incoming[_inIdx] = node; _inIdx += 1; if (IncomingFull) { _providerFullEvent.Set(); // Reader is Full _dataReceivedEvent.WaitOne(); // Wait for data to be picked up. } } private void Add(XamlNodeType nodeType, object data) { if (IsDisposed) { return; } if (nodeType != XamlNodeType.None) { AddToBuffer(new XamlNode(nodeType, data)); return; } Debug.Assert(XamlNode.IsEof_Helper(nodeType, data)); AddToBuffer(new XamlNode(XamlNode.InternalNodeType.EndOfStream)); _providerFullEvent.Set(); } private void AddLineInfo(int lineNumber, int linePosition) { if (IsDisposed) { return; } LineInfo lineInfo = new LineInfo(lineNumber, linePosition); XamlNode node = new XamlNode(lineInfo); AddToBuffer(node); } private XamlNode Next() { ObjectDisposedException.ThrowIf(IsDisposed, typeof(XamlBackgroundReader)); if (OutgoingEmpty) { // This is for users that read PAST the EOF record. // Don't let them hang on WaitOne() return EOF Again! if (_currentNode.IsEof) { return _currentNode; } _providerFullEvent.WaitOne(); // Wait for provider to fill up. SwapBuffers(); _dataReceivedEvent.Set(); // Let the Reader run. } _currentNode = _outgoing[_outIdx++]; if (_currentNode.IsEof) { if (_thread is not null) { // If the input ended due to an (caught) exception on the background thread, // then at the end of reading the input re-throw the exception on the // foreground thread. _thread.Join(); if (_caughtException is not null) { Exception ex = _caughtException; _caughtException = null; throw ex; } } } return _currentNode; } private XamlNode Next_ProcessLineInfo() { bool done = false; while (!done) { Next(); if (_currentNode.IsLineInfo) { _lineNumber = _currentNode.LineInfo.LineNumber; _linePosition = _currentNode.LineInfo.LinePosition; } else { done = true; } } return _currentNode; } private void InterruptableTransform(XamlReader reader, XamlWriter writer, bool closeWriter) { IXamlLineInfo xamlLineInfo = reader as IXamlLineInfo; IXamlLineInfoConsumer xamlLineInfoConsumer = writer as IXamlLineInfoConsumer; bool shouldPassLineNumberInfo = false; if ((xamlLineInfo is not null && xamlLineInfo.HasLineInfo) && (xamlLineInfoConsumer is not null && xamlLineInfoConsumer.ShouldProvideLineInfo)) { shouldPassLineNumberInfo = true; } while (reader.Read()) { if (IsDisposed) { break; } if (shouldPassLineNumberInfo) { if (xamlLineInfo.LineNumber != 0) { xamlLineInfoConsumer.SetLineInfo(xamlLineInfo.LineNumber, xamlLineInfo.LinePosition); } } writer.WriteNode(reader); } if (closeWriter) { writer.Close(); } } #region XamlReader public override bool Read() { return _internalReader.Read(); } public override XamlNodeType NodeType { get { return _internalReader.NodeType; } } public override bool IsEof { get { return _internalReader.IsEof; } } public override NamespaceDeclaration Namespace { get { return _internalReader.Namespace; } } public override XamlType Type { get { return _internalReader.Type; } } public override object Value { get { return _internalReader.Value; } } public override XamlMember Member { get { return _internalReader.Member; } } public override XamlSchemaContext SchemaContext { get { return _internalReader.SchemaContext; } } #endregion #region IXamlLineInfo Members public bool HasLineInfo { get { return _wrappedReaderHasLineInfo; } } public int LineNumber { get { return _lineNumber; } } public int LinePosition { get { return _linePosition; } } #endregion protected override void Dispose(bool disposing) { base.Dispose(disposing); _dataReceivedEvent.Set(); // Release any blocked writers. ((IDisposable)_dataReceivedEvent).Dispose(); ((IDisposable)_internalReader).Dispose(); ((IDisposable)_providerFullEvent).Dispose(); ((IDisposable)_writer).Dispose(); } } }