在任务异常的情况下,基于用户输入重试一次任务 [英] Retry a task multiple times based on user input in case of an exception in task

查看:120
本文介绍了在任务异常的情况下,基于用户输入重试一次任务的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我的应用程序中的所有服务调用都被实现为任务。当任务发生故障时,我需要向用户显示一个对话框,以重试最后一个操作失败。如果用户选择重试该程序应该重试该任务否则程序的执行应该在记录异常之后继续执行。任何人对如何实现这个功能有一个高层次的想法?

解决方案>

更新5/2017



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

 私有静态异步任务< T>重试< T>(Func< T> func,int retryCount)
{
while(true)
{
try
{
var result = await Task.Run(FUNC);
返回结果;
}
catch when(retryCount--> 0){}
}
}

和递归版本:

 私有静态异步任务< T>重试< T>(Func< T> func,int retryCount)
{
try
{
var result = await Task.Run(func);
返回结果;
}
catch when(retryCount--> 0){}
return await Retry(func,retryCount);
}

ORIGINAL



有很多方法可以对Retry函数进行编码:可以使用递归或任务迭代。希腊语.NET用户组中有一个讨论在不同的方式来做这个。

如果你使用F#,你也可以使用Async结构。不幸的是,至少在Async CTP中不能使用async / await结构,因为编译器生成的代码并不喜欢在catch块中进行多次等待或者可能的重新抛出。



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

  private static Task< T>重试< 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 =>
{
重试( 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> = 
让rec retry'retryCount =
async {
try
let! result = asyncComputation
return result
with exn - >
如果retryCount = 0,那么
return raise exn
else
return! retry'(retryCount - 1)
}
retry'retryCount

Unfortunatley,由于编译器不喜欢catch块中的等待语句,所以不可能使用Async CTP中的async / await在C#中编写类似的东西。以下尝试也会失败,因为运行时不喜欢在异常后遇到等待:

  private static async Task< T>重试< T>(Func< T> func,int retryCount)
{
while(true)
{
try
{
var result = await TaskEx.Run(FUNC);
返回结果;
}
catch
{
if(retryCount == 0)
throw;
retryCount--;
}
}
}

至于询问用户,您可以修改重试来调用一个函数,询问用户并通过TaskCompletionSource返回一个任务,以在用户回答时触发下一步,例如:

 code> private static Task< bool> AskUser()
{
var tcs = new TaskCompletionSource< bool>();
Task.Factory.StartNew(()=>
{
Console.WriteLine(@出现错误,继续?Y\N);
var response =控制台.ReadKey();
tcs.SetResult(response.KeyChar =='y');

});
return tcs.Task;
}

私有静态任务< T> RetryAsk< T>(Func< T> func,int retryCount,TaskCompletionSource< T> tcs = null)
{
if(tcs == null)
tcs = new TaskCompletionSource& );
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;
}

随着所有的延续,你可以看到为什么重试的异步版本是如此



更新:



在Visual Studio 2012 Beta版中,以下两个版本工作:



带有while循环的版本:

  private static异步任务< T>重试< T>(Func< T> func,int retryCount)
{
while(true)
{
try
{
var result = await Task.Run(FUNC);
返回结果;
}
catch
{
if(retryCount == 0)
throw;
retryCount--;
}
}
}

和递归版本: / p>

 私有静态异步任务< T>重试< T>(Func< T> func,int retryCount)
{
try
{
var result = await Task.Run(func);
返回结果;
}
catch
{
if(retryCount == 0)
throw;
}
return await Retry(func,--retryCount);
}


All the service calls in my application are implemented as tasks.When ever a task is faulted ,I need to present the user with a dialog box to retry the last operation failed.If the user chooses retry the program should retry the task ,else the execution of the program should continue after logging the exception.Any one has got a high level idea on how to implement this functionality ?

解决方案

UPDATE 5/2017

C# 6 exception filters make the catch clause a lot simpler :

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

and a recursive version:

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

ORIGINAL

There are many ways to code a Retry function: you can use recursion or task iteration. There was a discussion in the Greek .NET User group a while back on the different ways to do exactly this.
If you are using F# you can also use Async constructs. Unfortunately, you can't use the async/await constructs at least in the Async CTP, because the code generated by the compiler doesn't like multiple awaits or possible rethrows in catch blocks.

The recursive version is perhaps the simplest way to build a Retry in C#. The following version doesn't use Unwrap and adds an optional delay before retries :

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

The StartNewDelayed function comes from the ParallelExtensionsExtras samples and uses a timer to trigger a TaskCompletionSource when the timeout occurs.

The F# version is a lot simpler:

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

Unfortunatley, it isn't possible to write something similar in C# using async/await from the Async CTP because the compiler doesn't like await statements inside a catch block. The following attempt also fails silenty, because the runtime doesn't like encountering an await after an exception:

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

As for asking the user, you can modify Retry to call a function that asks the user and returns a task through a TaskCompletionSource to trigger the next step when the user answers, eg:

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

With all the continuations, you can see why an async version of Retry is so desirable.

UPDATE:

In Visual Studio 2012 Beta the following two versions work:

A version with a while loop:

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

and a recursive version:

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

这篇关于在任务异常的情况下,基于用户输入重试一次任务的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆