Esempio n. 1
0
            public TagSource(
                ITextView textViewOpt,
                ITextBuffer subjectBuffer,
                AbstractAsynchronousTaggerProvider <TTag> dataSource,
                IAsynchronousOperationListener asyncListener,
                IForegroundNotificationService notificationService)
            {
                if (dataSource.SpanTrackingMode == SpanTrackingMode.Custom)
                {
                    throw new ArgumentException("SpanTrackingMode.Custom not allowed.", "spanTrackingMode");
                }

                _subjectBuffer       = subjectBuffer;
                _textViewOpt         = textViewOpt;
                _dataSource          = dataSource;
                _asyncListener       = asyncListener;
                _notificationService = notificationService;
                _tagSpanComparer     = new TagSpanComparer(_dataSource.TagComparer);

                DebugRecordInitialStackTrace();

                _workQueue          = new AsynchronousSerialWorkQueue(asyncListener);
                this.CachedTagTrees = ImmutableDictionary.Create <ITextBuffer, TagSpanIntervalTree <TTag> >();

                _eventSource = CreateEventSource();

                Connect();

                // Kick off a task to compute the initial set of tags.
                RecalculateTagsOnChanged(new TaggerEventArgs(TaggerDelay.Short));
            }
Esempio n. 2
0
        public void TestMultipleBackgroundAction()
        {
            var exportProvider   = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider();
            var threadingContext = exportProvider.GetExportedValue <IThreadingContext>();
            var listenerProvider = exportProvider.GetExportedValue <IAsynchronousOperationListenerProvider>();

            // Test that background actions don't run at the same time.
            var worker    = new AsynchronousSerialWorkQueue(threadingContext, listenerProvider.GetListener("Test"));
            var doneEvent = new AutoResetEvent(false);

            var action1Ran = false;
            var action2Ran = false;

            worker.EnqueueBackgroundWork(() =>
            {
                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                action1Ran = true;

                // Simulate work to ensure that if tasks overlap that we will
                // see it.
                Thread.Sleep(1000);
                Assert.False(action2Ran);
            }, "Test", CancellationToken.None);

            worker.EnqueueBackgroundWork(() =>
            {
                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                action2Ran = true;
                doneEvent.Set();
            }, "Test", CancellationToken.None);

            doneEvent.WaitOne();
            Assert.True(action1Ran);
            Assert.True(action2Ran);
        }
Esempio n. 3
0
            public TagSource(
                ITextView textViewOpt,
                ITextBuffer subjectBuffer,
                AbstractAsynchronousTaggerProvider <TTag> dataSource,
                IAsynchronousOperationListener asyncListener,
                IForegroundNotificationService notificationService)
                : base(dataSource.ThreadingContext)
            {
                if (dataSource.SpanTrackingMode == SpanTrackingMode.Custom)
                {
                    throw new ArgumentException("SpanTrackingMode.Custom not allowed.", "spanTrackingMode");
                }

                _subjectBuffer       = subjectBuffer;
                _textViewOpt         = textViewOpt;
                _dataSource          = dataSource;
                _asyncListener       = asyncListener;
                _notificationService = notificationService;
                _tagSpanComparer     = new TagSpanComparer(_dataSource.TagComparer);

                DebugRecordInitialStackTrace();

                _workQueue          = new AsynchronousSerialWorkQueue(ThreadingContext, asyncListener);
                this.CachedTagTrees = ImmutableDictionary.Create <ITextBuffer, TagSpanIntervalTree <TTag> >();

                _eventSource = CreateEventSource();

                Connect();

                // Start computing the initial set of tags immediately.  We want to get the UI
                // to a complete state as soon as possible.
                ComputeInitialTags();
            }
            public TagComputer(
                ITextBuffer subjectBuffer,
                IForegroundNotificationService notificationService,
                IAsynchronousOperationListener asyncListener,
                ClassificationTypeMap typeMap,
                SyntacticClassificationTaggerProvider taggerProvider)
            {
                _subjectBuffer       = subjectBuffer;
                _notificationService = notificationService;
                _listener            = asyncListener;
                _typeMap             = typeMap;
                _taggerProvider      = taggerProvider;

                _workQueue = new AsynchronousSerialWorkQueue(asyncListener);
                _reportChangeCancellationSource = new CancellationTokenSource();

                _lastLineCache = new LastLineCache();

                _workspaceRegistration = Workspace.GetWorkspaceRegistration(subjectBuffer.AsTextContainer());
                _workspaceRegistration.WorkspaceChanged += OnWorkspaceRegistrationChanged;

                if (_workspaceRegistration.Workspace != null)
                {
                    ConnectToWorkspace(_workspaceRegistration.Workspace);
                }
            }
Esempio n. 5
0
        public void TestBackgroundAction()
        {
            var exportProvider =
                EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider();
            var threadingContext = exportProvider.GetExportedValue <IThreadingContext>();
            var listenerProvider =
                exportProvider.GetExportedValue <IAsynchronousOperationListenerProvider>();

            var worker = new AsynchronousSerialWorkQueue(
                threadingContext,
                listenerProvider.GetListener("Test")
                );
            var doneEvent = new AutoResetEvent(initialState: false);

            var actionRan = false;

            worker.EnqueueBackgroundWork(
                () =>
            {
                // Assert.NotNull(SynchronizationContext.Current);
                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                actionRan = true;
                doneEvent.Set();
            },
                GetType().Name + ".TestBackgroundAction",
                CancellationToken.None
                );

            doneEvent.WaitOne();
            Assert.True(actionRan);
        }
Esempio n. 6
0
        public void TestMultipleBackgroundAction()
        {
            // Test that background actions don't run at the same time.
            var worker    = new AsynchronousSerialWorkQueue(AsynchronousOperationListenerProvider.NullListener);
            var doneEvent = new AutoResetEvent(false);

            var action1Ran = false;
            var action2Ran = false;

            worker.EnqueueBackgroundWork(() =>
            {
                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                action1Ran = true;

                // Simulate work to ensure that if tasks overlap that we will
                // see it.
                Thread.Sleep(1000);
                Assert.False(action2Ran);
            }, "Test", CancellationToken.None);

            worker.EnqueueBackgroundWork(() =>
            {
                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                action2Ran = true;
                doneEvent.Set();
            }, "Test", CancellationToken.None);

            doneEvent.WaitOne();
            Assert.True(action1Ran);
            Assert.True(action2Ran);
        }
Esempio n. 7
0
            public TagComputer(
                ITextBuffer subjectBuffer,
                IForegroundNotificationService notificationService,
                IAsynchronousOperationListener asyncListener,
                ClassificationTypeMap typeMap,
                SyntacticClassificationTaggerProvider taggerProvider,
                IViewSupportsClassificationService viewSupportsClassificationServiceOpt,
                ITextBufferAssociatedViewService associatedViewService,
                IEditorClassificationService editorClassificationService,
                string languageName)
            {
                _subjectBuffer       = subjectBuffer;
                _notificationService = notificationService;
                _listener            = asyncListener;
                _typeMap             = typeMap;
                _taggerProvider      = taggerProvider;
                _viewSupportsClassificationServiceOpt = viewSupportsClassificationServiceOpt;
                _associatedViewService       = associatedViewService;
                _editorClassificationService = editorClassificationService;
                _languageName = languageName;

                _workQueue = new AsynchronousSerialWorkQueue(asyncListener);
                _reportChangeCancellationSource = new CancellationTokenSource();

                _lastLineCache = new LastLineCache();

                _workspaceRegistration = Workspace.GetWorkspaceRegistration(subjectBuffer.AsTextContainer());
                _workspaceRegistration.WorkspaceChanged += OnWorkspaceRegistrationChanged;

                ConnectToWorkspace(_workspaceRegistration.Workspace);
            }
Esempio n. 8
0
            public DiagnosticsTagSource(AbstractAggregatedDiagnosticsTagSource <TTag> owner)
            {
                _owner     = owner;
                _workQueue = new AsynchronousSerialWorkQueue(_owner.Listener);

                _lastDiagnostics = IntervalTree <Data> .Empty;
            }
Esempio n. 9
0
        public void TestBackgroundCancelMultipleActions()
        {
            var exportProvider   = EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider();
            var threadingContext = exportProvider.GetExportedValue <IThreadingContext>();
            var listenerProvider = exportProvider.GetExportedValue <IAsynchronousOperationListenerProvider>();

            // Ensure that multiple background actions are cancelled if they
            // use the same cancellation token.
            var worker = new AsynchronousSerialWorkQueue(threadingContext, listenerProvider.GetListener("Test"));

            var taskRunningEvent = new AutoResetEvent(false);
            var cancelEvent      = new AutoResetEvent(false);
            var doneEvent        = new AutoResetEvent(false);

            var source            = new CancellationTokenSource();
            var cancellationToken = source.Token;

            var action1Ran = false;
            var action2Ran = false;

            worker.EnqueueBackgroundWork(() =>
            {
                action1Ran = true;

                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                Assert.False(cancellationToken.IsCancellationRequested);

                taskRunningEvent.Set();
                cancelEvent.WaitOne();

                cancellationToken.ThrowIfCancellationRequested();
                Assert.True(false);
            }, "Test", source.Token);

            // We should not run this action.
            worker.EnqueueBackgroundWork(() =>
            {
                action2Ran = true;
                Assert.False(true);
            }, "Test", source.Token);

            taskRunningEvent.WaitOne();

            source.Cancel();
            cancelEvent.Set();

            try
            {
                worker.GetTestAccessor().WaitUntilCompletion();
                Assert.True(false);
            }
            catch (AggregateException ae)
            {
                Assert.IsAssignableFrom <OperationCanceledException>(ae.InnerException);
            }

            Assert.True(action1Ran);
            Assert.False(action2Ran);
        }
Esempio n. 10
0
        public ErrorsSnapshotFactory(IDiagnosticService diagnosticService, DocumentId documentId)
        {
            _diagnosticService = diagnosticService;
            _documentId        = documentId;

            _workQueue = new AsynchronousSerialWorkQueue(new AsynchronousOperationListener());

            _currentSnapshot = new ErrorsSnapshot(ImmutableArray <MappedDiagnostic> .Empty, 0);
        }
Esempio n. 11
0
        public void TestBackgroundCancelMultipleActions()
        {
            // Ensure that multiple background actions are cancelled if they
            // use the same cancellation token.
            var listener = new AggregateAsynchronousOperationListener(Enumerable.Empty <Lazy <IAsynchronousOperationListener, FeatureMetadata> >(), "Test");
            var worker   = new AsynchronousSerialWorkQueue(listener);

            var taskRunningEvent = new AutoResetEvent(false);
            var cancelEvent      = new AutoResetEvent(false);
            var doneEvent        = new AutoResetEvent(false);

            var source            = new CancellationTokenSource();
            var cancellationToken = source.Token;

            var action1Ran = false;
            var action2Ran = false;

            worker.EnqueueBackgroundWork(() =>
            {
                action1Ran = true;

                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                Assert.False(cancellationToken.IsCancellationRequested);

                taskRunningEvent.Set();
                cancelEvent.WaitOne();

                cancellationToken.ThrowIfCancellationRequested();
                Assert.True(false);
            }, "Test", source.Token);

            // We should not run this action.
            worker.EnqueueBackgroundWork(() =>
            {
                action2Ran = true;
                Assert.False(true);
            }, "Test", source.Token);

            taskRunningEvent.WaitOne();

            source.Cancel();
            cancelEvent.Set();

            try
            {
                worker.WaitUntilCompletion_ForTestingPurposesOnly();
                Assert.True(false);
            }
            catch (AggregateException ae)
            {
                Assert.IsAssignableFrom <OperationCanceledException>(ae.InnerException);
            }

            Assert.True(action1Ran);
            Assert.False(action2Ran);
        }
Esempio n. 12
0
        public CompilationAvailableTaggerEventSource(
            ITextBuffer subjectBuffer,
            IThreadingContext threadingContext,
            IAsynchronousOperationListener asyncListener,
            params ITaggerEventSource[] eventSources)
        {
            _subjectBuffer    = subjectBuffer;
            _asyncListener    = asyncListener;
            _underlyingSource = TaggerEventSources.Compose(eventSources);

            _workQueue = new AsynchronousSerialWorkQueue(threadingContext, asyncListener);
        }
Esempio n. 13
0
        protected TagSource(
            ITextBuffer subjectBuffer,
            IForegroundNotificationService notificationService,
            IAsynchronousOperationListener asyncListener)
        {
            this.SubjectBuffer   = subjectBuffer;
            _notificationService = notificationService;

            this.Listener  = asyncListener;
            this.WorkQueue = new AsynchronousSerialWorkQueue(asyncListener);

            StartInitialRefresh();
        }
Esempio n. 14
0
        public void TestBackgroundCancel1()
        {
            var exportProvider =
                EditorTestCompositions.EditorFeatures.ExportProviderFactory.CreateExportProvider();
            var threadingContext = exportProvider.GetExportedValue <IThreadingContext>();
            var listenerProvider =
                exportProvider.GetExportedValue <IAsynchronousOperationListenerProvider>();

            // Ensure that we can cancel a background action.
            var worker = new AsynchronousSerialWorkQueue(
                threadingContext,
                listenerProvider.GetListener("Test")
                );

            var taskRunningEvent = new AutoResetEvent(false);
            var cancelEvent      = new AutoResetEvent(false);
            var doneEvent        = new AutoResetEvent(false);

            var source            = new CancellationTokenSource();
            var cancellationToken = source.Token;

            var actionRan = false;

            worker.EnqueueBackgroundWork(
                () =>
            {
                actionRan = true;

                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                Assert.False(cancellationToken.IsCancellationRequested);

                taskRunningEvent.Set();
                cancelEvent.WaitOne();

                Assert.True(cancellationToken.IsCancellationRequested);

                doneEvent.Set();
            },
                "Test",
                source.Token
                );

            taskRunningEvent.WaitOne();

            source.Cancel();
            cancelEvent.Set();

            doneEvent.WaitOne();
            Assert.True(actionRan);
        }
Esempio n. 15
0
        public void TestBackgroundAction()
        {
            var worker    = new AsynchronousSerialWorkQueue(AsynchronousOperationListenerProvider.NullListener);
            var doneEvent = new AutoResetEvent(initialState: false);

            var actionRan = false;

            worker.EnqueueBackgroundWork(() =>
            {
                // Assert.NotNull(SynchronizationContext.Current);
                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                actionRan = true;
                doneEvent.Set();
            }, GetType().Name + ".TestBackgroundAction", CancellationToken.None);

            doneEvent.WaitOne();
            Assert.True(actionRan);
        }
            private int _isRequestPending; // int for Interlocked

            public TagComputer(
                ITextBuffer textBuffer,
                ClassificationTypeMap typeMap,
                IAsynchronousOperationListener asyncListener,
                SyntacticClassificationTaggerProvider taggerProvider)
            {
                _textBuffer     = textBuffer;
                _typeMap        = typeMap;
                _taggerProvider = taggerProvider;
                _workQueue      = new AsynchronousSerialWorkQueue(taggerProvider._threadingContext, asyncListener);

                _workspaceRegistration = Workspace.GetWorkspaceRegistration(textBuffer.AsTextContainer());
                _workspaceRegistration.WorkspaceChanged += OnWorkspaceRegistrationChanged;

                if (this._workspaceRegistration.Workspace != null)
                {
                    ConnectToWorkspace(this._workspaceRegistration.Workspace);
                }
            }
            public TagSource(
                ITextView textViewOpt,
                ITextBuffer subjectBuffer,
                AbstractAsynchronousTaggerProvider <TTag> dataSource,
                IAsynchronousOperationListener asyncListener,
                IForegroundNotificationService notificationService)
            {
                if (dataSource.SpanTrackingMode == SpanTrackingMode.Custom)
                {
                    throw new ArgumentException("SpanTrackingMode.Custom not allowed.", "spanTrackingMode");
                }

                _subjectBuffer       = subjectBuffer;
                _textViewOpt         = textViewOpt;
                _dataSource          = dataSource;
                _asyncListener       = asyncListener;
                _notificationService = notificationService;
                _tagSpanComparer     = new TagSpanComparer(_dataSource.TagComparer);

                DebugRecordInitialStackTrace();

                _workQueue          = new AsynchronousSerialWorkQueue(asyncListener);
                this.CachedTagTrees = ImmutableDictionary.Create <ITextBuffer, TagSpanIntervalTree <TTag> >();

                _eventSource = CreateEventSource();

                Connect();

                // Kick off a task to immediately compute the initial set of tags. This work should
                // not be cancellable (except if we get completely released), even if more events come
                // in.  That way we can get the initial set of results for the buffer as quickly as
                // possible, without kicking the work  down the road.
                var initialTagsCancellationToken = _initialComputationCancellationTokenSource.Token;

                // Note: we always kick this off to the new UI pump instead of computing tags right
                // on this thread.  The reason for that is that we may be getting created at a time
                // when the view itself is initializing.  As such the view is not in a state where
                // we want code touching it.
                RegisterNotification(
                    () => RecomputeTagsForeground(cancellationTokenOpt: initialTagsCancellationToken),
                    delay: 0,
                    cancellationToken: initialTagsCancellationToken);
            }
Esempio n. 18
0
        public void TestBackgroundAction()
        {
            var listener  = new AggregateAsynchronousOperationListener(Enumerable.Empty <Lazy <IAsynchronousOperationListener, FeatureMetadata> >(), "Test");
            var worker    = new AsynchronousSerialWorkQueue(listener);
            var doneEvent = new AutoResetEvent(initialState: false);

            var actionRan = false;

            worker.EnqueueBackgroundWork(() =>
            {
                // Assert.NotNull(SynchronizationContext.Current);
                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                actionRan = true;
                doneEvent.Set();
            }, GetType().Name + ".TestBackgroundAction", CancellationToken.None);

            doneEvent.WaitOne();
            Assert.True(actionRan);
        }
Esempio n. 19
0
        public ProjectionBufferService(
            IProjectionBufferFactoryService projectionBufferFactoryService,
            IContentTypeRegistryService contentTypeRegistryService,
            IForegroundNotificationService foregroundNotificationService,
            IAsynchronousOperationListener listener,
            Workspace workspace)
        {
            _projectionBufferFactoryService = projectionBufferFactoryService;

            _listener = listener;
            _foregroundNotificationService = foregroundNotificationService;

            _workspace = workspace;
            _workspace.DocumentOpened  += OnDocumentChanged;
            _workspace.DocumentChanged += OnDocumentChanged;

            _hlslContentType = contentTypeRegistryService.GetContentType(LanguageNames.Hlsl);

            _workQueue = new AsynchronousSerialWorkQueue(new AsynchronousOperationListener());
        }
Esempio n. 20
0
        public void TestBackgroundCancel1()
        {
            // Ensure that we can cancel a background action.
            var listener = new AggregateAsynchronousOperationListener(Enumerable.Empty <Lazy <IAsynchronousOperationListener, FeatureMetadata> >(), "Test");
            var worker   = new AsynchronousSerialWorkQueue(listener);

            var taskRunningEvent = new AutoResetEvent(false);
            var cancelEvent      = new AutoResetEvent(false);
            var doneEvent        = new AutoResetEvent(false);

            var source            = new CancellationTokenSource();
            var cancellationToken = source.Token;

            var actionRan = false;

            worker.EnqueueBackgroundWork(() =>
            {
                actionRan = true;

                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                Assert.False(cancellationToken.IsCancellationRequested);

                taskRunningEvent.Set();
                cancelEvent.WaitOne();

                Assert.True(cancellationToken.IsCancellationRequested);

                doneEvent.Set();
            }, "Test", source.Token);

            taskRunningEvent.WaitOne();

            source.Cancel();
            cancelEvent.Set();

            doneEvent.WaitOne();
            Assert.True(actionRan);
        }
 internal TestAccessor(AsynchronousSerialWorkQueue asynchronousSerialWorkQueue)
 {
     _asynchronousSerialWorkQueue = asynchronousSerialWorkQueue;
 }
Esempio n. 22
0
            public TagSource(
                ITextView textViewOpt,
                ITextBuffer subjectBuffer,
                AbstractAsynchronousTaggerProvider <TTag> dataSource,
                IAsynchronousOperationListener asyncListener,
                IForegroundNotificationService notificationService)
                : base(dataSource.ThreadingContext)
            {
                this.AssertIsForeground();
                if (dataSource.SpanTrackingMode == SpanTrackingMode.Custom)
                {
                    throw new ArgumentException("SpanTrackingMode.Custom not allowed.", "spanTrackingMode");
                }

                _subjectBuffer       = subjectBuffer;
                _textViewOpt         = textViewOpt;
                _dataSource          = dataSource;
                _asyncListener       = asyncListener;
                _notificationService = notificationService;

                _batchChangeTokenSource = new CancellationTokenSource();

                _batchChangeNotifier = new BatchChangeNotifier(
                    dataSource.ThreadingContext,
                    subjectBuffer, asyncListener, notificationService, NotifyEditorNow, _batchChangeTokenSource.Token);

                DebugRecordInitialStackTrace();

                _workQueue          = new AsynchronousSerialWorkQueue(ThreadingContext, asyncListener);
                this.CachedTagTrees = ImmutableDictionary.Create <ITextBuffer, TagSpanIntervalTree <TTag> >();

                _eventSource = CreateEventSource();

                Connect();

                // Start computing the initial set of tags immediately.  We want to get the UI
                // to a complete state as soon as possible.
                ComputeInitialTags();

                return;

                void Connect()
                {
                    this.AssertIsForeground();

                    _eventSource.Changed += OnEventSourceChanged;

                    if (_dataSource.TextChangeBehavior.HasFlag(TaggerTextChangeBehavior.TrackTextChanges))
                    {
                        _subjectBuffer.Changed += OnSubjectBufferChanged;
                    }

                    if (_dataSource.CaretChangeBehavior.HasFlag(TaggerCaretChangeBehavior.RemoveAllTagsOnCaretMoveOutsideOfTag))
                    {
                        if (_textViewOpt == null)
                        {
                            throw new ArgumentException(
                                      nameof(_dataSource.CaretChangeBehavior) + " can only be specified for an " + nameof(IViewTaggerProvider));
                        }

                        _textViewOpt.Caret.PositionChanged += OnCaretPositionChanged;
                    }

                    // Tell the interaction object to start issuing events.
                    _eventSource.Connect();
                }
            }
Esempio n. 23
0
        public void TestBackgroundCancelOneAction()
        {
            // Ensure that when a background action is cancelled the next
            // one starts (if it has a different cancellation token).
            var worker = new AsynchronousSerialWorkQueue(AsynchronousOperationListenerProvider.NullListener);

            var taskRunningEvent = new AutoResetEvent(false);
            var cancelEvent      = new AutoResetEvent(false);
            var doneEvent        = new AutoResetEvent(false);

            var source1 = new CancellationTokenSource();
            var source2 = new CancellationTokenSource();
            var token1  = source1.Token;
            var token2  = source2.Token;

            var action1Ran = false;
            var action2Ran = false;

            worker.EnqueueBackgroundWork(() =>
            {
                action1Ran = true;

                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                Assert.False(token1.IsCancellationRequested);

                taskRunningEvent.Set();
                cancelEvent.WaitOne();

                token1.ThrowIfCancellationRequested();
                Assert.True(false);
            }, "Test", source1.Token);

            worker.EnqueueBackgroundWork(() =>
            {
                action2Ran = true;

                Assert.NotSame(_foregroundSyncContext, SynchronizationContext.Current);
                Assert.False(token2.IsCancellationRequested);

                taskRunningEvent.Set();
                cancelEvent.WaitOne();

                doneEvent.Set();
            }, "Test", source2.Token);

            // Wait for the first task to start.
            taskRunningEvent.WaitOne();

            // Cancel it
            source1.Cancel();
            cancelEvent.Set();

            // Wait for the second task to start.
            taskRunningEvent.WaitOne();
            cancelEvent.Set();

            // Wait for the second task to complete.
            doneEvent.WaitOne();
            Assert.True(action1Ran);
            Assert.True(action2Ran);
        }