Multithreading exception and Dispose. Why Dispose didn't call?

旧巷老猫 提交于 2019-12-21 20:22:13

问题


'using' statement guaranteed that for the object will be called Dispose method. In this example this is not happening. And finalizer method didn't call too.

Why all this? And how I can change code for guaranteed disposing of my objects when exceptions on other threads can happens?

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
}

Output:

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

Lock C:\SomeFodler

Unhandled Exception: System.Exception: Unexpected exception at ExceptionExample.Program.ThreadTwo() in \visual studio 2010\Projects\ExceptionExample\ExceptionExample\Program.cs:line 36 at System.Threading.ThreadHelper.ThreadStart_Context(Object state) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state, Boolean ignoreSyncCtx) at System.Threading.ExecutionContext.Run(ExecutionContext executionContext, ContextCallback callback, Object state) at System.Threading.ThreadHelper.ThreadStart()

Outupt without exception:

\Visual Studio 2010\Projects\ExceptionExample\ExceptionExample\bin\Debug>ExceptionExample.exe Lock C:\SomeFodler UnLock C:\SomeFodler


回答1:


The unhandled exception forces the CLR to terminate the process. Shutdown behavior is slightly different for .NET 4.0, the finalizer will run after reporting the exception. But not in earlier versions.

You can work around this default behavior by writing an event handler for AppDomain.CurrentDomain.UnhandledException. Log or report the exception and call Environment.Exit(). That allows the finalizer thread to run and call your Unlock() method.

Do not rely on this, there are nasty exceptions like StackOverflow or FEEE that terminate the process anyway. So does somebody tripping over the power cord or shooting your process in the head with Taskmgr.exe




回答2:


Nothing is guaranteed; pulling the plug out or terminating a process won't respect using, for example. All that is guaranteed is that in normal execution (which includes most sane exceptions), it will call Dispose().

In your case, you have an unhandled thread exception; that is a process killer. All bets are off, since your process is now sickly and in the process of being put down (and out of it's misery).

If you want code to behave, you must ensure ou don't have process-killing exceptions; unhandled exceptions on threads being at the top of that list. A try/catch around any thread-level code is highly recommended.




回答3:


The reason is when your process terminates due to an unhandled exception the finalizers are not run. See here for more details. You can force to let your finalizers to run with the trick to shutdown your process normally within the unhandled exception handler from another thread. The policy of the .NET Framework is to do pretty much nothing when an unhandled exception occurs because it is not clear in which state the process is left. It may be unwise to process the finalizers since application state can be corrupt and during finalization also exceptions occur which would kill the finalizer thread as well. The net effect is that only some finalizers did run and the rest is left unprocessed. These followup exceptions do make it harder to find the root cause why the application did fail.

Yours, Alois Kraus




回答4:


If you run your threads on a BackgroundWorker then any exceptions that are thrown will be caught in the worker thread and will be returned as part of the object returned by the thread. Thus you won't need to worry about your exceptions escaping.

This creates another problem, that being that you cannot call Join on a BackgroundWorker, however you can add a Semaphore to the worker class with the counter set to 0 (blocked):

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

add a wait after you run your worker,

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

and add a Release in your worker code where you want to signal that you are done.

  workerFinished.Release() 



回答5:


As Marc said, once your app has unhandled exceptions, all bets are pretty much off. To clarify on what using is actually doing, it takes code like this:

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

and translates it into something like this:

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

So what happens when your application encounters an unhandled exception? The unhandled exception causes the application to be terminated. This termination (or any unexpected termination) can prevent the finally block from your using statement from executing properly.

You should always handle your exceptions, even in your case where you're not surrounding your threaded calls with try blocks. Consider hooking onto the AppDomain.UnhandledException event to clean up resources, log stuff, etc. before your application bites the dust.

EDIT

Just noticed Hans posted something similar regarding AppDomain.UnhandledException and he's right. It's been the case with any program that unexpected terminations can yield unexpected results. However, in your case, as has been suggested, don't rely on the full execution of your application to complete, especially with file resources. Rather, consider writing your process to anticipate, even expect, prior execution failures. Then your application can address incomplete executions as necessary. You can create logs to track the progress or mark of steps in your process and evaluate them on each run to address incorrect execution states.

Also, as another note, your classes (even considering the fact they're just samples) are not thread safe...you're not protecting your shared resources.



来源:https://stackoverflow.com/questions/5777204/multithreading-exception-and-dispose-why-dispose-didnt-call

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!