public async Task Run(CancellationToken cancellationToken)
        {
            try
            {
                var logger = PipelineContext.LoggerFactory?.CreateLogger(GetType());

                try
                {
                    logger?.LogInformation($"Starting pipeline step {StepNumber} of {PipelineContext.TotalSteps}");

                    await RunCore(cancellationToken);

                    logger?.LogInformation($"Completing pipeline step {StepNumber} of {PipelineContext.TotalSteps}");
                }

                catch (Exception e)
                {
                    logger?.LogError(e, $"Error while running pipeline step {StepNumber} of {PipelineContext.TotalSteps}");
                    throw;
                }
            }

            finally
            {
                //This statement is in place to ensure that no matter what, the output collection
                //will be marked "complete". Without this, an exception in the try block above can
                //lead to a stalled (i.e. non-terminating) pipeline because this thread's consumer
                //is waiting for more output from this thread, which will never happen because the
                //thread is now dead. This should also ensure we get at least partial output in case
                //of an exception.
                OutputCollection.CompleteAdding();
            }
        }