C#

如果任务异常,则根据用户输入多次重试任务

发布于 2021-02-02 16:10:41

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

关注者
0
被浏览
100
1 个回答
  • 面试哥
    面试哥 2021-02-02
    为面试而生,有面试问题,就找面试哥。

    更新5/2017

    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 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);
        }
    

    原版的

    编码Retry函数的方法有很多种:您可以使用递归或任务迭代。一段时间以来,希腊.NET用户组中进行了讨论讨论了执行此操作的不同方法。
    如果使用的是F#,则还可以使用异步构造。不幸的是,您至少不能在异步CTP中使用async /
    await构造,因为编译器生成的代码不喜欢多次等待或在catch块中重新抛出。

    递归版本可能是在C#中构建Retry的最简单方法。以下版本不使用Unwrap,而是在重试之前添加了可选的延迟:

    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;
        }
    

    所述StartNewDelayed函数来自ParallelExtensionsExtras样品和使用定时器发生超时时,以触发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
    

    不幸的是,不可能从Async CTP使用async /
    await在C#中写类似的东西,因为编译器不喜欢catch块中的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--;
                }
            }
        }
    

    至于询问用户,您可以修改重试以调用询问用户并通过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;
        }
    

    通过所有后续操作,您可以了解为什么非常需要异步版本的Retry。

    更新:

    在Visual Studio 2012 Beta中,以下两个版本适用:

    一个带有while循环的版本:

        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);
        }
    


知识点
面圈网VIP题库

面圈网VIP题库全新上线,海量真题题库资源。 90大类考试,超10万份考试真题开放下载啦

去下载看看