private async Task GetTask(TaskRunnerInformationBase info, CancellationToken ct)
        {
            Log?.LogVerbose(GetType(), nameof(RunTask), $"{info.LogPrefix} (CorrelationGroup [{_correlationGroup}]) - Starting task");

            DateTime taskStartTime  = DateTimeProvider.UtcNow;
            long     exceptionCount = 0;

            while (!ct.IsCancellationRequested)
            {
                try
                {
                    taskStartTime = DateTimeProvider.UtcNow;
                    await info.Function().ConfigureAwait(false);

                    Log?.LogVerbose(GetType(), nameof(RunTask), $"{info.LogPrefix} (CorrelationGroup [{_correlationGroup}]) - Task ran to completion");
                    return;
                }
                catch (Exception ex) when(!IsKillerException(ex, ct))
                {
                    var waitTime = Settings.GetWaitTime(exceptionCount);

                    //If below the MaxUnhandledExceptions count, we simply log a warning.
                    //Otherwise, if over the max, we attempt to handle via logging an error and calling Exceptionhandler.
                    if (!ShouldRespondToException(taskStartTime, GetRepeatedTaskFailureTime(ex, info, ct), ref exceptionCount, ct))
                    {
                        Log?.LogWarning(GetType(), nameof(RunTask), $"{info.LogPrefix} (CorrelationGroup [{_correlationGroup}]) - Restarting task in [{waitTime.TotalSeconds}s] after [{exceptionCount}] exceptions due to unhandled exception: [{ex}]");
                    }
                    else
                    {
                        //Log an error, adding exception data to aid in debugging.
                        Log?.LogError(GetType(), nameof(RunTask), ex, $"{info.LogPrefix} (CorrelationGroup [{_correlationGroup}]) - Restarting task in [{waitTime.TotalSeconds}s] after [{exceptionCount}] exceptions due to unhandled exception.");

                        //If an exceptionHandler was provided for the task, call it.
                        if (info.ExceptionHandler != null)
                        {
                            try
                            {
                                await info.ExceptionHandler(ex, info).ConfigureAwait(false);
                            }
                            catch (Exception iex) when(!IsKillerException(ex, ct))
                            {
                                Log?.LogWarning(GetType(), nameof(RunTask), $"{info.LogPrefix} (CorrelationGroup [{_correlationGroup}]) - Unhandled exception in final exception handler: [{iex}]");
                            }
                        }
                    }

                    //Increment the exception count regardless of scenario, and wait for an appropriate period
                    exceptionCount++;
                    await Task.Delay(waitTime, ct).ConfigureAwait(false);
                }
            }
        }
 /// <summary>
 /// Gets the minimum amount of time that a Task should be able to run before encoutering an unhandled exception
 /// If an exception is encountered by a Task within this time period after it is restarted, we will start to back
 /// off and spend more time in betwen Task restarts.
 /// </summary>
 /// <param name="ex"></param>
 /// <param name="info"></param>
 /// <param name="ct"></param>
 /// <returns></returns>
 private TimeSpan GetRepeatedTaskFailureTime(Exception ex, TaskRunnerInformationBase info, CancellationToken ct)
 {
     if (info.RepeatedTaskFailureTimeHandler != null)
     {
         try
         {
             return(info.RepeatedTaskFailureTimeHandler(ex, Settings.DefaultRepeatedTaskFailureTime));
         }
         catch (Exception iex) when(!IsKillerException(ex, ct))
         {
             // Don't want this to throw out, it will go immediately to the final exception. Return the default TimeSpan.
             Log?.LogWarning(GetType(), nameof(GetRepeatedTaskFailureTime), $"{info.LogPrefix} (CorrelationGroup [{_correlationGroup}]) - Unhandled exception: [{iex}]");
         }
     }
     return(Settings.DefaultRepeatedTaskFailureTime);
 }
        private Task RunTask(TaskRunnerInformationBase info, CancellationToken ct)
        {
            var task = Task.Run(async() =>
            {
                await GetTask(info, ct).ConfigureAwait(false);
            });

            task.ContinueWith(t =>
            {
                // Tasks should only fault on Partition Killer exceptions
                // when this happens, cancel all tasks and throw any exceptions
                Cancel();
            },
                              ct,
                              TaskContinuationOptions.ExecuteSynchronously | TaskContinuationOptions.OnlyOnFaulted,
                              TaskScheduler.Current
                              );

            return(task);
        }