C# 如果任务中出现异常,请根据用户输入重试任务多次

C# 如果任务中出现异常,请根据用户输入重试任务多次,c#,wpf,exception-handling,task-parallel-library,task,C#,Wpf,Exception Handling,Task Parallel Library,Task,我的应用程序中的所有服务调用都作为任务实现。当任务出现故障时,我需要向用户提供一个对话框,以重试上次失败的操作。如果用户选择重试,程序应重试该任务,否则,在记录异常后,程序应继续执行。任何人都对如何实现此功能有较高的认识?在较高的层次上,我发现根据您拥有的和想要的内容制作函数签名很有帮助 你有: 为您提供任务的函数(Func)。我们将使用该函数,因为任务本身通常不可重试 确定整个任务是否已完成或应重试的函数(Func) 你想要: 总体任务 因此,您将拥有如下函数: Task Retry(

我的应用程序中的所有服务调用都作为任务实现。当任务出现故障时,我需要向用户提供一个对话框,以重试上次失败的操作。如果用户选择重试,程序应重试该任务,否则,在记录异常后,程序应继续执行。任何人都对如何实现此功能有较高的认识?

在较高的层次上,我发现根据您拥有的和想要的内容制作函数签名很有帮助

你有:

  • 为您提供任务的函数(
    Func
    )。我们将使用该函数,因为任务本身通常不可重试
  • 确定整个任务是否已完成或应重试的函数(
    Func
你想要:

  • 总体任务
因此,您将拥有如下函数:

Task Retry(Func<Task> action, Func<Task, bool> shouldRetry);
这里明显的问题是,只会发生1次重试。为了避免这种情况,您需要为函数自身调用提供一种方法。使用lambdas执行此操作的通常方法如下:

//error checking
var result = new TaskCompletionSource<object>();

Func<Task, Task> retryRec = null; //declare, then assign
retryRec = (t) => { if (shouldRetry(t))
                        return action().ContinueWith(retryRec).Unwrap();
                    else
                    {
                        if (t.IsFaulted) 
                            result.TrySetException(t.Exception);
                        //and so on
                        return result.Task; //need to return something
                     }
                  };
 action().ContinueWith(retryRec);
 return result.Task;
//错误检查
var result=new TaskCompletionSource();
Func retryRec=null//声明,然后分配
retryRec=(t)=>{if(shouldRetry(t))
返回操作().ContinueWith(retryRec.Unwrap();
其他的
{
如果(t.IsFaulted)
结果:trysetexeception(t.Exception);
//等等
return result.Task;//需要返回一些内容
}
};
action().ContinueWith(retryRec);
返回结果。任务;

2017年5月更新

C#6异常过滤器使
catch
子句简单得多:

    private static async Task<T> Retry<T>(Func<T> func, int retryCount)
    {
        while (true)
        {
            try
            {
                var result = await Task.Run(func);
                return result;
            }
            catch when (retryCount-- > 0){}
        }
    }
private静态异步任务重试(Func-Func,int-retryCount)
{
while(true)
{
尝试
{
var result=wait Task.Run(func);
返回结果;
}
当(retryCount-->0){}时捕获
}
}
和递归版本:

    private static async Task<T> Retry<T>(Func<T> func, int retryCount)
    {
        try
        {
            var result = await Task.Run(func);
            return result;
        }
        catch when (retryCount-- > 0){}
        return await Retry(func, retryCount);
    }
    private static async Task<T> Retry<T>(Func<T> func, int retryCount)
    {
        try
        {
            var result = await Task.Run(func);
            return result;
        }
        catch
        {
            if (retryCount == 0)
                throw;
        }
        return await Retry(func, --retryCount);
    }
private静态异步任务重试(Func-Func,int-retryCount)
{
尝试
{
var result=wait Task.Run(func);
返回结果;
}
当(retryCount-->0){}时捕获
返回等待重试(func,retryCount);
}
原创

编写重试函数的方法有很多:可以使用递归或任务迭代。希腊.NET用户组a中有一个用户组,他们使用不同的方法来实现这一点。
如果使用F#,还可以使用异步构造。不幸的是,至少在异步CTP中不能使用async/await构造,因为编译器生成的代码不喜欢catch块中的多次等待或可能的重试

递归版本可能是在C#中构建重试的最简单方法。以下版本不使用“展开”,并在重试前添加可选延迟:

private static Task<T> Retry<T>(Func<T> func, int retryCount, int delay, TaskCompletionSource<T> tcs = null)
    {
        if (tcs == null)
            tcs = new TaskCompletionSource<T>();
        Task.Factory.StartNew(func).ContinueWith(_original =>
        {
            if (_original.IsFaulted)
            {
                if (retryCount == 0)
                    tcs.SetException(_original.Exception.InnerExceptions);
                else
                    Task.Factory.StartNewDelayed(delay).ContinueWith(t =>
                    {
                        Retry(func, retryCount - 1, delay,tcs);
                    });
            }
            else
                tcs.SetResult(_original.Result);
        });
        return tcs.Task;
    } 
private静态任务重试(Func-Func,int-retryCount,int-delay,TaskCompletionSource tcs=null)
{
如果(tcs==null)
tcs=新任务完成源();
Task.Factory.StartNew(func).ContinueWith(\u original=>
{
如果(_original.IsFaulted)
{
如果(retryCount==0)
SetException(_original.Exception.innerException);
其他的
Task.Factory.StartNewDelayed(延迟)。ContinueWith(t=>
{
重试(func,retryCount-1,delay,tcs);
});
}
其他的
tcs.SetResult(_原始结果);
});
返回tcs.Task;
} 
该函数来自示例,并在超时发生时使用计时器触发TaskCompletionSource

F#版本要简单得多:

let retry (asyncComputation : Async<'T>) (retryCount : int) : Async<'T> = 
let rec retry' retryCount = 
    async {
        try
            let! result = asyncComputation  
            return result
        with exn ->
            if retryCount = 0 then
                return raise exn
            else
                return! retry' (retryCount - 1)
    }
retry' retryCount
让我们重试(异步计算:异步=
让rec重试'retryCount=
异步的{
尝试
让!result=异步计算
返回结果
使用exn->
如果retryCount=0,则
返回上升exn
其他的
return!retry'(retryCount-1)
}
重试“retryCount”
不幸的是,不可能使用异步CTP中的async/await在C#中编写类似的代码,因为编译器不喜欢catch块中的await语句。以下尝试也会失败,因为运行时不喜欢在异常后遇到await:

private static async Task<T> Retry<T>(Func<T> func, int retryCount)
    {
        while (true)
        {
            try
            {
                var result = await TaskEx.Run(func);
                return result;
            }
            catch 
            {
                if (retryCount == 0)
                    throw;
                retryCount--;
            }
        }
    }
private静态异步任务重试(Func-Func,int-retryCount)
{
while(true)
{
尝试
{
var result=wait TaskEx.Run(func);
返回结果;
}
抓住
{
如果(retryCount==0)
投掷;
重新计数--;
}
}
}
至于询问用户,您可以修改Retry以调用一个函数,该函数询问用户并通过TaskCompletionSource返回任务,以在用户应答时触发下一步,例如:

 private static Task<bool> AskUser()
    {
        var tcs = new TaskCompletionSource<bool>();
        Task.Factory.StartNew(() =>
        {
            Console.WriteLine(@"Error Occured, continue? Y\N");
            var response = Console.ReadKey();
            tcs.SetResult(response.KeyChar=='y');

        });
        return tcs.Task;
    }

    private static Task<T> RetryAsk<T>(Func<T> func, int retryCount,  TaskCompletionSource<T> tcs = null)
    {
        if (tcs == null)
            tcs = new TaskCompletionSource<T>();
        Task.Factory.StartNew(func).ContinueWith(_original =>
        {
            if (_original.IsFaulted)
            {
                if (retryCount == 0)
                    tcs.SetException(_original.Exception.InnerExceptions);
                else
                    AskUser().ContinueWith(t =>
                    {
                        if (t.Result)
                            RetryAsk(func, retryCount - 1, tcs);
                    });
            }
            else
                tcs.SetResult(_original.Result);
        });
        return tcs.Task;
    } 
private静态任务AskUser()
{
var tcs=new TaskCompletionSource();
Task.Factory.StartNew(()=>
{
Console.WriteLine(@“发生错误,是否继续?Y\N”);
var response=Console.ReadKey();
SetResult(response.KeyChar=='y');
});
返回tcs.Task;
}
私有静态任务RetryAsk(Func Func,int retryCount,TaskCompletionSource tcs=null)
{
如果(tcs==null)
tcs=新任务完成源();
Task.Factory.StartNew(func).ContinueWith(\u original=>
{
如果(_original.IsFaulted)
{
如果(retryCount==0)
tcs.S
    private static async Task<T> Retry<T>(Func<T> func, int retryCount)
    {
        while (true)
        {
            try
            {
                var result = await Task.Run(func);
                return result;
            }
            catch
            {
                if (retryCount == 0)
                    throw;
                retryCount--;
            }
        }
    }
    private static async Task<T> Retry<T>(Func<T> func, int retryCount)
    {
        try
        {
            var result = await Task.Run(func);
            return result;
        }
        catch
        {
            if (retryCount == 0)
                throw;
        }
        return await Retry(func, --retryCount);
    }
static Task<T> RetryWhile<T>(
    Func<int, Task<T>> func, 
    Func<Exception, int, bool> shouldRetry )
{
    return RetryWhile<T>( func, shouldRetry, new TaskCompletionSource<T>(), 0, Enumerable.Empty<Exception>() );
}

static Task<T> RetryWhile<T>( 
    Func<int, Task<T>> func, 
    Func<Exception, int, bool> shouldRetry, 
    TaskCompletionSource<T> tcs, 
    int previousAttempts, IEnumerable<Exception> previousExceptions )
{
    func( previousAttempts ).ContinueWith( antecedent =>
    {
        if ( antecedent.IsFaulted )
        {
            var antecedentException = antecedent.Exception;
            var allSoFar = previousExceptions
                .Concat( antecedentException.Flatten().InnerExceptions );
            if ( shouldRetry( antecedentException, previousAttempts ) )
                RetryWhile( func,shouldRetry,previousAttempts+1, tcs, allSoFar);
            else
                tcs.SetException( allLoggedExceptions );
        }
        else
            tcs.SetResult( antecedent.Result );
    }, TaskContinuationOptions.ExecuteSynchronously );
    return tcs.Task;
}