General Retry Strategy #3 with TryResult
Let’s put the cart before the horse – I mean Catch before the Try :)

class Program
{
    static void Main(string[] args)
    {
        using (new Catch&lt;InvalidOperationException&gt;())
        using (new Catch&lt;FormatException&gt;(ex =&gt; Console.WriteLine("Oops!")))
            Try.Retry(100, 3, 3).Execute(() =&gt;
            {
                Console.WriteLine("Trying!");
                throw new FormatException();
            });
    }
}


These using Catch statements define exceptions we can tolerate and continue trying. All other exceptions will be interpreted as critical. We can also rethrow or log in the optional handler.

Library classes:

public class Catch&lt;TException&gt; : Catch
    where TException : Exception
{
    public Catch()
        : this(ex =&gt; { })
    {
    }

    public Catch(Action&lt;TException&gt; handler)
    {
        Handler = handler;
    }

    protected internal override bool HandleCore(Exception ex)
    {
        if (ex is TException)
        {
            Handler(ex as TException);
            return true;
        }

        if (Previous == null)
            throw ex;
        else
            return Previous.HandleCore(ex);
    }

    Action&lt;TException&gt; Handler { get; }
}


And:

public abstract class Catch : Ambient&lt;Catch&gt;
{
    public static void Handle(Exception ex) =&gt;
        Current?.HandleCore(ex);

    protected internal abstract bool HandleCore(Exception ex);
}


And:

public abstract class Ambient&lt;T&gt; : IDisposable where T : Ambient&lt;T&gt;
{
    static readonly string Id = typeof(T).FullName;
    protected static T Current
    {
        get { return (T)CallContext.LogicalGetData(Id); }
        set { CallContext.LogicalSetData(Id, value); }
    }

    protected Ambient()
    {
        Previous = Current;
        Current = (T)this;
    }

    public void Dispose() =&gt; Current = Previous;
    protected T Previous { get; }
}


And we also need to update Retry strategy:

    public override void Execute(Action action, CancellationToken cancellationToken)
    {
        foreach (var delay in Delays)
            try
            {
                action();
                return;
            }
            catch(Exception ex)
            {
                Catch.Handle(ex); // &lt;- NEW LINE
                cancellationToken.WaitHandle.WaitOne(delay);
                cancellationToken.ThrowIfCancellationRequested();
            }

        action();
    }




UPDATE

Let's have this for the Log class - note Log.IfFail():

class Log : Ambient&lt;Log&gt;
{
    public static Exception IfFail(Action action)
    {
        try
        {
            action();
            return null;
        }
        catch(Exception ex)
        {
            Write(ex.Message);
            return ex;
        }
    }

    public static void Write(string line) =&gt;
        Current?.WriteCore(line);

    public Log(string fileName)
    {
        FileName = fileName;
    }

    void WriteCore(string line) =&gt; File.AppendAllText(FileName, line);
    string FileName { get; }
}


And:

public static class Error
{
    public static void Rethrow(this Exception ex)
    {
        if (ex != null)
            throw ex;
    }
}


So we can have TryExecute:

    public override bool TryExecute(Action action, CancellationToken cancellationToken)
    {
        foreach (var delay in Delays)
            try
            {
                Log.IfFail(action).Rethrow();
                return true;
            }
            catch(Exception ex)
            {
                Catch.Handle(ex);
                cancellationToken.WaitHandle.WaitOne(delay);
                cancellationToken.ThrowIfCancellationRequested();
            }

        return Log.IfFail(action) == null; // yes, ugly :) let's invent something better
    }


We can pass more context for logging at the moment of ambient log construction.



UPDATE #2 :)

public static class Error
{
    public static void Rethrow(this Exception ex)
    {
        if (ex != null)
            throw ex;
    }

    public static bool ToTryResult(this Exception ex)
    {
        return ex == null;
    }
}


And:

    public override bool TryExecute(Action action, CancellationToken cancellationToken)
    {
        foreach (var delay in Delays)
            try
            {
                Log.IfFail(action).Rethrow();
                return true;
            }
            catch(Exception ex)
            {
                Catch.Handle(ex);
                cancellationToken.WaitHandle.WaitOne(delay);
                cancellationToken.ThrowIfCancellationRequested();
            }

        return Log.IfFail(action).ToTryResult();
    }