Могу ли я поймать исключение типа, переданного в универсальном параметре в C #? - PullRequest
0 голосов
/ 30 августа 2018

Возьмем, к примеру, этот код - я думаю, что когда появится 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 (тот, что в методе retry) никогда не срабатывает.

Ответы [ 2 ]

0 голосов
/ 30 августа 2018

Есть ли вероятность, что это проблема асинхронности?

Как отмечено в другом ответе, да, это асинхронная проблема.

Асинхронные и итераторные блоки в C # являются сопрограммами . Нормальная процедура может выполнять три вещи: выполнить до конца, бросить или войти в бесконечный цикл Сопрограмма может сделать четвертое: приостановить, чтобы возобновить позже . await - это точка в асинхронном блоке, где происходит приостановка; yield return в блоке итератора.

В вашем случае бросок не происходит, пока сопрограмма не возобновится, и в этот момент 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 не будет вызвана для итератора; все, что он делает, это возвращает итератор. Сопрограмма асинхронного блока приостанавливает в ожидании , но не требуется для приостановки в ожидании; В ожидании уже выполненного задания можно пропустить приостановку.

попробовать с сопрограммами сложно; будь осторожен!

0 голосов
/ 30 августа 2018

Я думаю, что ваша проблема вызвана тем, что ваш func является асинхронным. Я бы попробовал что-то подобное для async func s

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, поэтому оно выдает его исключение.

Редактировать: Полный пример кода, включая асинхронный бегун для всего Task против Task<T>

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...