Многопоточность исключения и избавления.Почему Dispose не звонил? - PullRequest
3 голосов
/ 25 апреля 2011

Использование оператора гарантирует, что для объекта будет вызван метод Dispose. В этом примере этого не происходит. И метод финализатора тоже не вызывал.

Зачем все это? И как я могу изменить код для гарантированной утилизации моих объектов, когда могут происходить исключения в других потоках?

class Program
{
    static void Main(string[] args)
    {
        Thread th1 = new Thread(ThreadOne);
        Thread th2 = new Thread(ThreadTwo);

        th1.Start();
        th2.Start();

        th1.Join();
        th2.Join();
    }

    static void ThreadOne()
    {
        using (LockedFolder lf = new LockedFolder(@"C:\SomeFodler"))
        {
            // some pay load
            Thread.Sleep(5000);
        }
    }

    static void ThreadTwo()
    {
        // some pay load
        Thread.Sleep(1000);
        throw new Exception("Unexpected exception");
    }
}

public class LockedFolder : IDisposable
{
    private const string FILENAME_LOCK = ".lock-file";
    private bool bLocked = false;

    public string FullPath { private set; get; }

    public LockedFolder(string FullPath)
    {
        this.FullPath = FullPath;
        Lock();
    }

    private void Lock()
    {
        // lock our folder
        Console.WriteLine("Lock " + FullPath);

        //CreateLockFile(Path + FILENAME_LOCK);
        bLocked = true;
    }

    private void UnLock()
    {
        if (!bLocked)
        {
            Console.WriteLine("Already UnLocked " + FullPath);
            return; // already unlocked
        }

        Console.WriteLine("UnLock " + FullPath);

        // unlock our folder
        //DeleteLockFile(Path + FILENAME_LOCK);
        bLocked = false;
    }

    #region IDisposable Members

    private bool disposed = false;

    public void Dispose()
    {
        Dispose(true);
        GC.SuppressFinalize(this);
    }

    public void Dispose(bool disposing)
    {
        if (!disposed)
        {
            if (disposing)
            {
                // Free managed resources

            }

            // Free unmanaged resource
            UnLock();
        }

        disposed = true;
    }

    ~LockedFolder()
    {
        Dispose(false);
    }

    #endregion
}

Выход:

\ Visual Studio 2010 \ Projects \ ExceptionExample \ ExceptionExample \ Bin \ Debug> ExceptionExample.exe

Блокировка C: \ SomeFodler

Необработанное исключение: System.Exception: Неожиданное исключение при ExceptionExample.Program.ThreadTwo () в \ визуальной студии 2010 \ Projects \ ExceptionExample \ ExceptionExample \ Program.cs: линии 36 в System.Threading.ThreadHelper.ThreadStart_Context (Объект состояние) в System.Threading.ExecutionContext.Run (ExecutionContext executeContext, ContextCallback обратный вызов, состояние объекта, логическое значение ignoreSyncCtx) в System.Threading.ExecutionContext.Run (ExecutionContext executeContext, ContextCallback обратный вызов, состояние объекта) в System.Threading.ThreadHelper.ThreadStart ()

Outupt без исключения:

\ Visual Studio 2010 \ Projects \ ExceptionExample \ ExceptionExample \ Bin \ Debug> ExceptionExample.exe Блокировка C: \ SomeFodler Разблокировка C: \ SomeFodler

Ответы [ 5 ]

4 голосов
/ 25 апреля 2011

Необработанное исключение вынуждает CLR завершить процесс.Завершение работы немного отличается для .NET 4.0, финализатор будет работать после сообщения об исключении.Но не в более ранних версиях.

Вы можете обойти это поведение по умолчанию, написав обработчик событий для AppDomain.CurrentDomain.UnhandledException.Войдите или сообщите об исключении и вызовите Environment.Exit ().Это позволяет потоку финализатора запускаться и вызывать ваш метод Unlock ().

Не полагайтесь на это, есть неприятные исключения, такие как StackOverflow или FEEE, которые в любом случае завершают процесс.Так же, как кто-то спотыкается о шнур питания или стреляет вам в голову с помощью Taskmgr.exe

4 голосов
/ 25 апреля 2011

Ничто не гарантировано ; например, выдергивание вилки из розетки или прекращение процесса не будут уважать using. Все, что гарантировано, это то, что в обычном исполнении (что включает в себя большинство нормальных исключений), он будет вызывать Dispose().

В вашем случае у вас есть необработанное исключение потока; это убийца процесса. Все ставки сняты, так как ваш процесс сейчас болезненный и находится в процессе подавления (и из-за его страданий).

Если вы хотите, чтобы код работал, вы должны убедиться, что у вас нет исключений, убивающих процессы; необработанные исключения для потоков, находящихся в верхней части этого списка. Настоятельно рекомендуется try / catch вокруг любого кода уровня потока.

1 голос
/ 25 апреля 2011

Причина в том, что когда ваш процесс завершается из-за необработанного исключения, финализаторы не запускаются.Смотрите здесь для более подробной информации.Вы можете заставить ваши финализаторы работать с хитростью, чтобы нормально завершить ваш процесс в обработчике необработанных исключений из другого потока.Политика .NET Framework заключается в том, чтобы почти ничего не делать при возникновении необработанного исключения, поскольку неясно, в каком состоянии находится процесс.Может быть неразумно обрабатывать финализаторы, поскольку состояние приложения может быть повреждено, и во время финализации также возникают исключения, которые также убивают поток финализатора.Чистый эффект состоит в том, что только некоторые финализаторы работали, а остальные остаются необработанными.Эти исключительные ситуации затрудняют поиск основной причины сбоя приложения.

С уважением, Алоис Краус

0 голосов
/ 25 апреля 2011

Как сказал Марк, как только в вашем приложении появятся необработанные исключения, все ставки в значительной степени отклоняются.Чтобы выяснить, что на самом деле делает using, он берет код, подобный следующему:

using(var myDisposableObject = GetDisposableObject()) {
    // Do stuff with myDisposableObject
}

, и переводит его в нечто вроде этого:

MyDisposableObject myDisposableObject;
try {
    myDisposableObject = GetDisposableObject();
    // Do stuff with myDisposableObject
}
finally {
    if(myDisposableObject != null) {
        myDisposableObject.Dispose();
    }
}

Итак, что происходит, когда ваше приложение встречаетнеобработанное исключение?Необработанное исключение приводит к прекращению работы приложения.Это завершение (или любое неожиданное завершение) может помешать правильному выполнению блока finally из вашего оператора using.

Вы должны всегда обрабатывать свои исключения, даже в том случае, когда вы не окружаете свой потокзвонки с try блоками.Подумайте о подключении к событию AppDomain.UnhandledException , чтобы очистить ресурсы, журналы и т. Д. До того, как ваше приложение уничтожит пыль.

РЕДАКТИРОВАТЬ

Только что заметил, что Ганс опубликовал нечто подобное в отношении AppDomain.UnhandledException, и он прав.Это было в случае с любой программой, что неожиданные завершения могут дать неожиданные результаты.Однако в вашем случае, как уже было предложено, не полагайтесь на полное выполнение вашего приложения, особенно с файловыми ресурсами.Скорее рассмотрите возможность написания вашего процесса, чтобы предвидеть, даже ожидать, предшествующие сбои выполнения.Тогда ваше приложение может обращаться к незавершенным выполнениям по мере необходимостиВы можете создавать журналы для отслеживания хода выполнения или отметки шагов в вашем процессе и оценивать их при каждом запуске для устранения некорректных состояний выполнения.

Также, как еще одно примечание, ваши классы (даже учитывая тот факт, что они простопримеры) не являются потокобезопасными ... вы не защищаете свои общие ресурсы.

0 голосов
/ 25 апреля 2011

Если вы выполняете ваши потоки на BackgroundWorker, то любые исключения, которые выбрасываются, будут перехвачены в рабочем потоке и будут возвращены как часть объекта, возвращаемого потоком.Таким образом, вам не нужно беспокоиться о том, что исключение может быть исключено.

Это создает еще одну проблему, заключающуюся в том, что вы не можете вызвать Join на BackgroundWorker, однако вы можете добавить Semaphore к рабочему классу со счетчиком, установленным в 0 (заблокирован):

  private Semaphore workerFinsished = new Semaphore(0, 1);

добавьте ожидание после запуска вашего работника,

  public void Join() { workerFinished.WaitOne(); }

и добавьте Release в свойрабочий код, где вы хотите сообщить, что вы сделали.

  workerFinished.Release() 
...