Уклончивые исключения и «истекло время оценки функции» - PullRequest
3 голосов
/ 19 февраля 2012

Фон

В моей библиотеке утилит (Shd.dll) у меня есть класс AsyncOperation.Проще говоря, это базовый класс для типов, которые инкапсулируют потенциально длительную операцию, выполняют ее в фоновом потоке и поддерживают паузу / возобновление, отмену и отчеты о ходе выполнения.(Это похоже на BackgroundWorker, просто он знает больше вещей.)

В коде пользователя вы можете использовать его так:

class MyOperation : AsyncOperation
{
    public MyOperation() : base(null, AsyncOperationOptions.Cancelable | AsyncOperationOptions.Pausable) {}

    protected override void RunOperation(AsyncOperationState operationState, object userState)
    {
         ...
         operationState.ThrowIfCancelled();
    }
}

 var op = new MyOperation();
 op.Start();
 ...
 op.Cancel();

operationState.ThrowIfCancelled () делает именно то, что предполагает его имя:если метод Cancel () был вызван ранее другим потоком, он генерирует внутреннее исключение (AsyncOperationCancelException), которое затем обрабатывается типом AsyncOperation, например так:

private void _DoExecute(object state)
{
    // note that this method is already executed on the background thread
    ...
    try
    {
        operationDelegate.DynamicInvoke(args); // this is where RunOperation() is called
    }
    catch(System.Reflection.TargetInvocationException tiex)
    {
        Exception inner = tiex.InnerException;
        var cancelException = inner as AsyncOperationCancelException;
        if(cancelException != null)
        {
             // the operation was cancelled
             ...
        }
        else
        {
            // the operation faulted
            ...
        }
        ...
    }
    ...
}

Это работает отлично.Или так я думал в прошлом году, когда использовал это в многочисленных сценариях.

Актуальная проблема

Я создаю класс, который использует System.Net.WebClient для загрузки потенциально больших файлов.количество файлов через FTP.Этот класс построен с использованием базового класса AsyncOperation, как описано выше.

Для точных отчетов о ходе работы я использую WebClient.UploadFileAsync (), что усложняет код, но соответствующие части выглядят так:

private ManualResetEventSlim completedEvent = new ManualResetEventSlim(false);

private void WebClient_UploadProgressChanged(object sender, UploadProgressChangedEventArgs e)
{
    ...
    if (OperationState.IsCancellationRequested)
    {
        _GetCurrentWebClient().CancelAsync();
    }
}

private void WebClient_UploadFileCompleted(object sender, UploadFileCompletedEventArgs e)
{
    ...
    _UploadNextFile();
}

private void _UploadNextFile()
{
    if (OperationState.IsCancellationRequested || ...)
    {
        this.completedEvent.Set();
        return;
    }
    ...
}

protected override void RunOperation(AsyncOperationState operationState, object userState)
{
    ...
    _UploadNextFile();
    this.completedEvent.Wait();

    operationState.ThrowIfCancelled(); // crash
    ...
}

Как видите, я отметил линию, где происходит сбой.Что именно происходит, так это то, что когда выполнение попадает в эту строку (я ставлю точку останова прямо над ней, поэтому я знаю, что это точная строка), Visual Studio 2010 останавливается примерно на 15 секунд, а затем следующее, что я вижу, - это исходный кодof AsyncOperationState.ThrowIfCancelled ():

public void ThrowIfCancelled()
{
    if(IsCancellationRequested)
    {
        throw new AsyncOperationCancelException();
    }
} // this is the line the debugger highlights: "An exception of type AsyncOperationCancelException' occured in Shd.dll but was unhandled by user code."

Я пытался поместить точки останова туда, где должно было быть поймано исключение, но выполнение никогда не достигает этого блока catch {}.

Другой странный этов конце он также пишет следующее: «Оценка функции отключена, потому что истек срок ожидания предыдущей оценки функции».Я погуглил эту проблему и попробовал все, что было предложено (отключил неявную оценку свойства, удалил все точки останова), но пока ничего не помогло.

Вот два скриншота, иллюстрирующих проблему:
http://dl.dropbox.com/u/17147594/vsd1.png
http://dl.dropbox.com/u/17147594/vsd2.png

Я использую .NET 4.0.Буду очень признателен за любую помощь.

Ответы [ 3 ]

4 голосов
/ 22 февраля 2012

Когда отладчик Visual Studio подключен к приложению, он получает уведомление всякий раз, когда выдается исключение, прежде чем исполняемый код получит возможность его обработать.Это называется исключение первого шанса , и VS можно настроить на прерывание выполнения при возникновении определенного типа исключения.

Вы можете указать поведение отладчика для каждоготип исключения отдельно с помощью окна Exceptions (меню отладки).По умолчанию для всех исключений установлен флажок «Необработанный пользователь», что означает, что только необработанные исключения прервут выполнение.Установка флажка «Брошено» для определенного типа исключения заставляет VS прервать выполнение, даже если исключение будет обработано, но только для этого типа исключения (не для производных типов).Если обработчик существует, как только вы возобновите выполнение (нажав F5), исключение будет перехвачено как обычно.

Я бы предположил, что ваше пользовательское исключение было добавлено в список исключений в окне Исключения (которое вы можетепроверьте с помощью кнопки Find в окне).

[Изменить]

Согласно моим тестам, это также происходит, когда DynamicInvoke используется в .NET 4 , независимо от настройки окна Exceptions.Вчера я использовал VS2008 и не смог воспроизвести его, но сейчас это выглядит странно.

Это тест, который я пробовал (извините за краткое форматирование, но он довольно прост):

Action<int> a = i => { throw new ArgumentException(); };

// When the following code is executed, VS2010 debugger
// will break on the `ArgumentException` above 
// but ONLY if the target is .NET 4 (3.5 and lower don't break)
try { a.DynamicInvoke(5); }
catch (Exception ex)
{ }

// this doesn't break
try { a.Invoke(5); }
catch (Exception ex)
{ }

// neither does this
try { a(5); }
catch (Exception ex)
{ }

Мое единственное предположение, что обработка исключений внутри InvokeMethodFast (то есть InternalCall метод) как-то изменилась.DynamicInvoke код изменился между версиями 4 и более ранними, но нет ничего, что указывало бы на то, почему отладчик VS2010 не может увидеть, что внутри вызова этого метода есть обработчик исключений.

2 голосов
/ 20 февраля 2012

«истекло время оценки функции»

У вас нет реальной проблемы, это артефакт отладчика.Это вызвано тем, как отладчик оценивает выражения наблюдения.Когда вы запускаете программу .NET с подключенным отладчиком, программа будет иметь специальный поток, предназначенный исключительно для использования отладчиком.Всякий раз, когда отладчику необходимо оценить выражение наблюдения, он использует этот поток для выполнения кода выражения.Затем результат отображается в окне просмотра.

, которое работает очень хорошо и дает отладчику множество возможностей.Включая вызов метода в вашей программе, когда активна точка останова.Скорее необходимо, многие значения объектов, которые вас интересуют, представлены в виде свойств.Которые реализованы как методы в сгенерированном коде.

Этот поток отладчика, однако, может вызвать проблемы.Очевидный случай, когда вы пытаетесь оценить свойство, которое принимает блокировку.Если вы прервете выполнение на этапе, когда эта блокировка принадлежит другому потоку, тогда поток отладчика столкнется с кирпичной стеной.Некоторое время он разворачивается, замечает, что поток отладчика не завершается, затем сдается и отображает «тайм-аут оценки функции» в качестве значения наблюдения.Он также помнит, что произошел сбой, любые часы, которые вы попробуете позже, приведут к «Оценка функции отключена, потому что истекло время ожидания предыдущей функции».Обязательно, поток отладчика все еще застрял в колее.

Подобная проблема в вашем коде.Вероятный сценарий - то, что поток, который вам нужен, чтобы завершить операцию, был приостановлен прерыванием отладчика.Единственный достойный совет, который следует здесь дать, - это быть осторожным с выражением часов.

1 голос
/ 20 февраля 2012

Если ваша логика try catch работает в потоке , отличном от , чем код, который фактически выдает исключение, то блок catch никогда не будет выполнен.

Рассмотрим следующий пример:

class Program
{
    static void Main(string[] args)
    {
        try
        {
            Thread thread = new Thread((s) =>
                {
                    throw new Exception("Blah");
                });

            thread.Start();
        }
        catch (Exception ex)
        {
            Console.WriteLine("Exception caught: {0}", ex);
        }

        Console.ReadKey();
    }
}

Блок catch, конечно, не выполняется, так как исключение было сгенерировано в другом потоке. Console crashes!

Я подозреваю, что вы могли испытывать нечто подобное. Если вы хотите, чтобы ваш блок catch выполнялся, он должен находиться в том же потоке, на который выдается ошибка.

Удачи!

...