Example #1
0
        /// <summary>
        /// Get the tags for the specified SnapshotSpan in a background task.  If there are outstanding
        /// requests for SnapshotSpan values then this one will take priority over those
        /// </summary>
        private void GetTagsInBackground(SnapshotSpan span)
        {
            var synchronizationContext = SynchronizationContext.Current;

            if (null == synchronizationContext)
            {
                return;
            }

            // The background processing should now be focussed on the specified ITextSnapshot
            var snapshot = span.Snapshot;

            // In the majority case GetTags(NormalizedSnapshotCollection) drives this function and
            // AdjustToSnapshot is already called.  There are other code paths though within AsyncTagger
            // which call this method.  We need to guard against them here.
            AdjustToSnapshot(snapshot);

            // Our caching and partitioning of data is all done on a line range
            // basis.  Just expand the requested SnapshotSpan to the encompassing
            // SnaphotlineRange
            var lineRange = SnapshotLineRange.CreateForSpan(span);

            span = lineRange.ExtentIncludingLineBreak;

            // If we already have a background task running for this ITextSnapshot then just enqueue this
            // request onto that existing one.  By this point if the request exists it must be tuned to
            // this ITextSnapshot
            if (_asyncBackgroundRequest.HasValue)
            {
                var asyncBackgroundRequest = _asyncBackgroundRequest.Value;
                if (asyncBackgroundRequest.Snapshot == snapshot)
                {
                    Contract.Requires(asyncBackgroundRequest.Snapshot == snapshot);
                    EditorUtilsTrace.TraceInfo("AsyncTagger Background Existing {0} - {1}", lineRange.StartLineNumber, lineRange.LastLineNumber);
                    asyncBackgroundRequest.Channel.WriteNormal(lineRange);
                    return;
                }

                CancelAsyncBackgroundRequest();
            }

            Contract.Assert(!_asyncBackgroundRequest.HasValue);
            EditorUtilsTrace.TraceInfo("AsyncTagger Background New {0} - {1}", lineRange.StartLineNumber, lineRange.LastLineNumber);

            // Create the data which is needed by the background request
            var data = _asyncTaggerSource.GetDataForSnapshot(snapshot);
            var cancellationTokenSource = new CancellationTokenSource();
            var cancellationToken       = cancellationTokenSource.Token;
            var channel = new Channel();

            channel.WriteNormal(lineRange);

            // If there is an ITextView then make sure it is requested as well.  If the source provides an
            // ITextView then it is always prioritized on requests for a new snapshot
            if (_asyncTaggerSource.TextViewOptional != null)
            {
                var visibleLineRange = _asyncTaggerSource.TextViewOptional.GetVisibleSnapshotLineRange();
                if (visibleLineRange.HasValue)
                {
                    channel.WriteVisibleLines(visibleLineRange.Value);
                }
            }

            // The background thread needs to know the set of values we have already queried
            // for.  Send a copy since this data will be mutated from a background thread
            NormalizedLineRangeCollection localVisited;

            if (_tagCache.BackgroundCacheData.HasValue)
            {
                var backgroundCacheData = _tagCache.BackgroundCacheData.Value;
                Contract.Requires(backgroundCacheData.Snapshot == snapshot);
                localVisited = backgroundCacheData.VisitedCollection.Copy();
            }
            else
            {
                localVisited = new NormalizedLineRangeCollection();
            }

            // Function which finally gets the tags.  This is run on a background thread and can
            // throw as the implementor is encouraged to use CancellationToken::ThrowIfCancelled
            var    localAsyncTaggerSource = _asyncTaggerSource;
            var    localChunkCount        = _chunkCount;
            Action getTags = () => GetTagsInBackgroundCore(
                localAsyncTaggerSource,
                data,
                localChunkCount,
                channel,
                localVisited,
                cancellationToken,
                (completeReason) => synchronizationContext.Post(_ => OnGetTagsInBackgroundComplete(completeReason, channel, cancellationTokenSource), null),
                (processedLineRange, tagList) => synchronizationContext.Post(_ => OnGetTagsInBackgroundProgress(cancellationTokenSource, processedLineRange, tagList), null));

            // Create the Task which will handle the actual gathering of data.  If there is a delay
            // specified use it
            var    localDelay = _asyncTaggerSource.Delay;
            Action taskAction =
                () =>
            {
                if (localDelay.HasValue)
                {
                    Thread.Sleep(localDelay.Value);
                }

                getTags();
            };

            var task = new Task(taskAction, cancellationToken);

            _asyncBackgroundRequest = new AsyncBackgroundRequest(
                span.Snapshot,
                channel,
                task,
                cancellationTokenSource);

            task.Start();
        }