Exceptions in multithreaded application.

2020-06-16 01:31发布

问题:

I have heard from a very discerning person that an exception being thrown (and not caught) in a thread is being propagated to the parent thread. Is that true? I have tried something like this but couldn't catch the exception in the creating thread.

    static void Main(string[] args)
    {
        ParameterizedThreadStart pts = 
           new ParameterizedThreadStart(ThreadMethod);
        try
        {
            Thread t = new Thread(pts);
            t.Start(new object());
            Console.ReadLine();
        }
        catch (Exception ex) //the exception is not caught
        {
            Debugger.Break();
        }
    }


    static void ThreadMethod(object @object)
    {
        Thread.Sleep(2000);
        throw new IndexOutOfRangeException();
        Thread.CurrentThread.Abort();
    }

回答1:

The thread's exception will not propogate to the main thread's context. This really makes sense - by the time the exception is thrown, the main thread will typically be in a completely different scope than the one containing your exception handler.

You can catch these exceptions (typically to log them) by hooking into AppDomain.UnhandledException. See that page for details, including differences in Windows Forms applications, etc.



回答2:

This is a great article about Threading in C# and how to handle Exceptions



回答3:

No, it won't. In order to catch thread exceptions you need to use Application.ThreadException.



回答4:

A decent resource on Threading in C# that has a section for exception handling: Threading in C#



回答5:

Exception propagate upwards in the call-stack.

If you start a new thread from a certain method, it will propagate upwards until it gets to that method.

If that method doesn't catch it, you'll get a runtime error that there's an uncaught exception.



回答6:

Here is one way of catching it and handling it in a safe way:

BackgroundWorker bg = new BackgroundWorker();
object o;
bg.DoWork += (c1,c2) =>
{         
        Thread.Sleep(2000);        
        throw new IndexOutOfRangeException();
};

bg.RunWorkerCompleted += (c3,c4) => 
{

 if (((RunWorkerCompletedEventArgs)e).Error != null)
    {
        MessageBox.Show(((RunWorkerCompletedEventArgs)e).Error.Message);
    }
};


回答7:

In short, no it doesn't. You have a couple of obvious options:#

  1. log it out in the method your new thread starts (top of the stack for the created thread) in.
  2. Use a construct like Asynchronous delegate, which will will return an exception when you call end invoke, which you can then catch in the normal way.


回答8:

An exception can only be caught on the thread from which is came. So throwing an exception on another thread won't cause another thread to catch it.

There is no concept of a "parent" thread.



回答9:

There is a very simple solution for the special case when the main thread waits for the completion of the other thread (see https://msdn.microsoft.com/en-us/library/58195swd(v=vs.110).aspx#Examples). It requires only a class exception variable.

The modified code sample completed by an exception handler may look like this.

using System;
using System.Threading;

class WaitOne
{
    static AutoResetEvent autoEvent = new AutoResetEvent(false);
    static Exception SavedException = null;

    static void Main()
    {
        Console.WriteLine("Main starting.");

        ThreadPool.QueueUserWorkItem(
            new WaitCallback(WorkMethod), autoEvent);

        // Wait for work method to signal.
        autoEvent.WaitOne();
        if (SavedException != null)
        {
           // handle this exception as you want
        }

        Console.WriteLine("Work method signaled.\nMain ending.");
    }

    static void WorkMethod(object stateInfo) 
    {
        Console.WriteLine("Work starting.");

        // Simulate time spent working.
        try
        {
          Thread.Sleep(new Random().Next(100, 2000));
          throw new Exception("Test exception");
        }
        catch (Exception ex)
        {
            SavedException = ex;
        }            

        // Signal that work is finished.
        Console.WriteLine("Work ending.");
        ((AutoResetEvent)stateInfo).Set();
    }
}


回答10:

We can use CustomExceptionHandler for this. Below code might help you.

    using System;
    using System.Threading;
    using System.Windows.Forms;

    // Create a form with a button that, when clicked, throws an exception.
     public class ErrorForm : System.Windows`enter code here`.Forms.Form
     {
        internal Button button1;

        public ErrorForm() : base()
        {
           // Add the button to the form.
           this.button1 = new System.Windows.Forms.Button();
           this.SuspendLayout();
           this.button1.Location = new System.Drawing.Point(100, 43);
           this.button1.Size = new System.Drawing.Size(75, 23);
           this.button1.Text = "Click!";
           this.Controls.Add(this.button1);
           this.button1.Click += new EventHandler(this.button1_Click);

           this.Text = "ThreadException";
           this.ResumeLayout(false);
        }

        // Throw an exception when the button is clicked.
        private void button1_Click(object sender, System.EventArgs e)
        {
           throw new ArgumentException("The parameter was invalid");
        }

        public static void Main()
        {
           // Add the event handler.
           Application.ThreadException += new ThreadExceptionEventHandler(CustomExceptionHandler.OnThreadException);

           // Start the example.
           Application.Run(new ErrorForm());
        }
     }

     // Create a class to handle the exception event.
     internal class CustomExceptionHandler
     {
         // Handle the exception event
        public static void OnThreadException(object sender, ThreadExceptionEventArgs t)
        {
           DialogResult result = ShowThreadExceptionDialog(t.Exception);

           // Exit the program when the user clicks Abort.
           if (result == DialogResult.Abort) 
              Application.Exit();
        }

        // Create and display the error message.
        private static DialogResult ShowThreadExceptionDialog(Exception e)
        {
           string errorMsg = "An error occurred.  Please contact the adminstrator " +
                "with the following information:\n\n";
           errorMsg += String.Format("Exception Type: {0}\n\n", e.GetType().Name);
           errorMsg += "\n\nStack Trace:\n" + e.StackTrace;
           return MessageBox.Show(errorMsg, "Application Error", 
                MessageBoxButtons.AbortRetryIgnore, MessageBoxIcon.Stop);
        }
     }

2nd approach:-

using System;
using System.IO;
using System.Threading.Tasks;

class Example
{
   static async Task Main(string[] args)
   {
      // Get a folder path whose directories should throw an UnauthorizedAccessException.
      string path = Directory.GetParent(
                              Environment.GetFolderPath(
                              Environment.SpecialFolder.UserProfile)).FullName;

      // Use this line to throw UnauthorizedAccessException, which we handle.
      Task<string[]> task1 = Task<string[]>.Factory.StartNew(() => GetAllFiles(path));

      // Use this line to throw an exception that is not handled.
      // Task task1 = Task.Factory.StartNew(() => { throw new IndexOutOfRangeException(); } );
      try {
          await task1;
      }
      catch (AggregateException ae) {
          ae.Handle((x) =>
          {
              if (x is UnauthorizedAccessException) // This we know how to handle.
              {
                  Console.WriteLine("You do not have permission to access all folders in this path.");
                  Console.WriteLine("See your network administrator or try another path.");
                  return true;
              }
              return false; // Let anything else stop the application.
          });
      }

      Console.WriteLine("task1 Status: {0}{1}", task1.IsCompleted ? "Completed," : "", 
                                                task1.Status);
   }

   static string[] GetAllFiles(string str)
   {
      // Should throw an UnauthorizedAccessException exception.
      return System.IO.Directory.GetFiles(str, "*.txt", System.IO.SearchOption.AllDirectories);
   }
}