使用Reactive Extensions重试异步任务代码

时间:2017-01-11 21:08:20

标签: c# system.reactive reactiveui

在我的数据访问类中使用以下代码。

public async Task<IEnumerable<TEntity>> QueryAsync(string sql, object param = null,
            CommandType commandType = CommandType.Text, int? commandTimeout = null, IDbTransaction transaction = null)
        {
            using (var connection = Connection)
            {
                var tokenSource = GetCancellationTokenSource(commandTimeout ?? CommandTimeoutDefault);
                Task<IEnumerable<TEntity>> queryTask =
                    connection.QueryAsync<TEntity>(new CommandDefinition(sql, param, transaction,
                        commandTimeout ?? CommandTimeoutDefault, commandType, cancellationToken: tokenSource.Token));
                IEnumerable<TEntity> data = await queryTask.ConfigureAwait(false);
                connection.Close();
                connection.Dispose();
                tokenSource.Dispose();
                return data;
            }
        }

我想要一个SqlExeption抛出一次重试。请记住,我不能将RX应用于应用程序,而只能应用于此代码块。

我尝试了下面的代码,看起来它正在正确执行,Do正在登录控制台输出,但并没有真正调用Catch处理程序而我没有确定是否也执行Retry处理程序。

public async Task<IEnumerable<TEntity>> QueryAsync(string sql, object param = null,
            CommandType commandType = CommandType.Text, int? commandTimeout = null, IDbTransaction transaction = null)
        {
            return await Observable.Defer(async () =>
            {
                using (var connection = Connection)
                {
                    var tokenSource = GetCancellationTokenSource(commandTimeout ?? CommandTimeoutDefault);
                    Task<IEnumerable<TEntity>> queryTask =
                        connection.QueryAsync<TEntity>(new CommandDefinition(sql, param, transaction,
                            commandTimeout ?? CommandTimeoutDefault, commandType, cancellationToken: tokenSource.Token));
                    IEnumerable<TEntity> data = await queryTask.ConfigureAwait(false);
                    connection.Close();
                    connection.Dispose();
                    tokenSource.Dispose();
                    return Observable.Return(data);
                }
            })
            .Catch<IEnumerable<TEntity>, SqlException>(source =>
           {
               Debug.WriteLine($"QueryAsync Exception {source}");
               return Observable.Return(new List<TEntity>());
           })
           .Throttle(TimeSpan.FromMilliseconds(500))
           .Retry(1)
           .Do(_ => Debug.WriteLine("Do QueryAsync"));
        }

1 个答案:

答案 0 :(得分:6)

我可以看到您的代码存在几个潜在问题:

  • 例如,在名为QueryWithRetryAsync的方法中,将重试逻辑与主逻辑分开。这只是一个设计问题,但仍然存在问题
  • Catch 之后 Retry。否则,SqlException将导致空列表,Retry运算符将永远不会看到异常
  • 我认为Throttle根本不是必需的,因为你只想通过管道获得一个值
  • Retry(1)没有按照你的想法做到(这对我来说也是一个惊喜)。似乎“重试”的定义包括第一次调用,因此您需要Retry(2)

以下是一个独立的示例,其行为方式符合您的要求:

class Program
{
    static void Main(string[] args)
    {
        var pipeline = Observable
            .Defer(() => DoSomethingAsync().ToObservable())
            .Retry(2)
            .Catch<string, InvalidOperationException>(ex => Observable.Return("default"));

        pipeline
            .Do(Console.WriteLine)
            .Subscribe();

        Console.ReadKey();
    }

    private static int invocationCount = 0;

    private static async Task<string> DoSomethingAsync()
    {
        Console.WriteLine("Attempting DoSomethingAsync");

        await Task.Delay(TimeSpan.FromSeconds(2));

        ++invocationCount;

        if (invocationCount == 2)
        {
            return "foo";
        }

        throw new InvalidOperationException();
    }
}