我可以捕获c#中泛型参数传入的类型的异常吗?

问题描述 投票:1回答:2

以此代码为例 - 我认为当显示TException时,我应该能够“捕获”它并重试我的func()适当的次数。但是当我把这些代码放在野外时,即使抛出TException类型的异常,它也会跳过catch子句并冒泡。有人可以解释原因吗?

public static T TryNTimes<T, TException>(Func<T> func, int times) where TException : Exception
{
    if (times <= 0)
        throw new ArgumentException($"TryNTimes: `times` must be a positive integer. You passed in: {times}");

    while (times > 0)
    {
        try
        {
            return func();
        }
        catch (TException)
        {
            if (--times <= 0)
                throw;
        }
    }

    // should never reach here
    return default(T);
}

代码被调用如下:

await RetryUtils.TryNTimes<Task, MyCustomException>(
        () => TryHandleElasticMappingError(dataGridResults, dataGridRecords),
        MyFieldsCount)
    .ConfigureAwait(false);

这是一个异步问题吗?上面的行包含在Try-Catch中,它捕获了Exception,我能够验证异常的类型是MyCustomException。我可以确认内部catch块(重试方法中的那个)永远不会被击中。

c# .net generics exception
2个回答
0
投票

我认为你的问题是由于你的func是异步的。我会尝试这样的异步funcs

using System;
using System.Threading.Tasks;

namespace GenericException
{

    public static class FuncHelpers
    {
        public static async Task<T> TryNTimesAsync<T,TException>(this Func<Task<T>> func, int n) where TException : Exception
        {
            while(n --> 0)
            {
                try
                {
                    return await func();
                }
                catch(TException)
                {
                    if(n < 0)
                        throw;
                }
            }
            return default(T);
        }

        public static async Task TryNTimesAsync<TException>(this Func<Task> func, int n) where TException : Exception
        {
            while(n --> 0)
            {
                try
                {
                    await func();
                }
                catch(TException)
                {
                    if(n <= 0) throw;
                }
            }
        }

        public static T TryNTimes<T,TException>(this Func<T> func, int n) where TException : Exception
        {
            while(n --> 0)
            {
                try
                {
                    return func();
                }
                catch(TException)
                {
                    if(n <= 0) throw;
                }
            }
            return default(T);
        }
    }

    class Program
    {

        static Task AddTwoNumbersAsync(int num1, int num2)
        {
            var task = new Task(() =>
            {
                if(num1 % 2 == 0) throw new ArgumentException($"{nameof(num1)} must be odd");
                Console.WriteLine($"{num1} + {num2} = {num1 + num2}");
            });
            task.Start();
            return task;
        }

        static async Task Main(string[] args)
        {
            Func<Task> addTask = () => AddTwoNumbersAsync(2, 4);
            Console.WriteLine("trying to add non async");
            var taskResult = addTask.TryNTimes<Task, ArgumentException>(5);
            Console.WriteLine("trying to add async");
            try
            {
                await addTask.TryNTimesAsync<ArgumentException>(5);
            }
            catch(ArgumentException)
            {
                Console.WriteLine("There was an error trying to add async");
            }
        }
    }
}

至于为什么它不按预期工作,我同意@Hans Kilian。

基本上,你的func正在返回一个Task,或承诺做某事。那个东西在Task完成之前并不一定完成。您在此方法之外捕获异常的原因是当您调用.ConfigureAwait(false)时,您正在等待Task完成,因此它将抛出异常。

编辑:完整示例代码,包括TaskTask<T>的异步运行程序


6
投票

这是一个异步问题吗?

如其他答案中所述,是的,这是一个异步问题。

C#中的异步和迭代器块是协同程序。正常的例程可以做三件事:运行完成,抛出或进入无限循环。一个协程可以做第四件事:暂停,以后再恢复。 await是异步区块中发生暂停的点;它是迭代器块中的yield return

在你的情况下,直到协程恢复,投掷才会发生,此时try不再生效; try的方法运行完成,因为它不是协程。如果你想让try生效,那么try也必须在异步块中,你必须在try中使用await

同样,如果你写道:

IEnumerable<int> Weird(bool b)
{
  if (b) throw new Exception();
  yield return 1;
}

...
IEnumerable<int> x = null;
try
{
  x = Weird(true); // Should throw, right?
}
catch(Exception ex)
{
  // Nope; this is unreachable
}
foreach(int y in x) // the throw happens here!

迭代器块协程开始挂起,并且在迭代器上调用MoveNext之前不会恢复;它只是返回一个迭代器。异步块协程在await上挂起,但不需要在await上挂起;等待已经完成的任务被允许跳过暂停。

使用协同程序尝试捕获是棘手的;小心!

© www.soinside.com 2019 - 2024. All rights reserved.