[c#] 다른 스레드에서 발생하는 예외 포착

내 방법 중 하나 ( Method1)는 새 스레드를 생성합니다. 해당 스레드는 메서드 ( Method2)를 실행하고 예외 중에 예외가 발생합니다. 호출 메서드 ( Method1) 에 대한 예외 정보를 가져와야합니다.

어떻게 Method1든 던져진 이 예외를 잡을 수 Method2있습니까?



답변

에서 .NET 4 이상, 당신은 사용할 수 있습니다 Task<T>새 스레드를 만드는 대신 클래스를. 그런 다음 .Exceptions작업 개체의 속성을 사용하여 예외를 가져올 수 있습니다 . 두 가지 방법이 있습니다.

  1. 별도의 메서드에서 : // 일부 작업의 스레드 에서 예외를 처리 합니다.

    class Program
    {
        static void Main(string[] args)
        {
            Task<int> task = new Task<int>(Test);
            task.ContinueWith(ExceptionHandler, TaskContinuationOptions.OnlyOnFaulted);
            task.Start();
            Console.ReadLine();
        }
    
        static int Test()
        {
            throw new Exception();
        }
    
        static void ExceptionHandler(Task<int> task)
        {
            var exception = task.Exception;
            Console.WriteLine(exception);
        }
    }
  2. 같은 방법으로 : // 호출자의 스레드 에서 예외를 처리 합니다.

    class Program
    {
        static void Main(string[] args)
        {
            Task<int> task = new Task<int>(Test);
            task.Start();
    
            try
            {
                task.Wait();
            }
            catch (AggregateException ex)
            {
                Console.WriteLine(ex);
            }
    
            Console.ReadLine();
        }
    
        static int Test()
        {
            throw new Exception();
        }
    }

당신이 얻는 예외는 AggregateException. 모든 실제 예외는 ex.InnerExceptions재산을 통해 이용 가능 합니다.

.NET 3.5 에서는 다음 코드를 사용할 수 있습니다.

  1. // 자식 스레드 에서 예외를 처리 합니다.

    class Program
    {
        static void Main(string[] args)
        {
            Exception exception = null;
            Thread thread = new Thread(() => SafeExecute(() => Test(0, 0), Handler));
            thread.Start();
    
            Console.ReadLine();
        }
    
        private static void Handler(Exception exception)
        {
            Console.WriteLine(exception);
        }
    
        private static void SafeExecute(Action test, Action<Exception> handler)
        {
            try
            {
                test.Invoke();
            }
            catch (Exception ex)
            {
                Handler(ex);
            }
        }
    
        static void Test(int a, int b)
        {
            throw new Exception();
        }
    }
  2. 또는 // 호출자의 스레드 에서 예외를 처리 합니다.

    class Program
    {
        static void Main(string[] args)
        {
            Exception exception = null;
            Thread thread = new Thread(() => SafeExecute(() => Test(0, 0), out exception));
    
            thread.Start();
    
            thread.Join();
    
            Console.WriteLine(exception);
    
            Console.ReadLine();
        }
    
        private static void SafeExecute(Action test, out Exception exception)
        {
            exception = null;
    
            try
            {
                test.Invoke();
            }
            catch (Exception ex)
            {
                exception = ex;
            }
        }
    
        static void Test(int a, int b)
        {
            throw new Exception();
        }
    }

답변

Method1에서 예외를 포착 할 수 없습니다. 그러나 Method2에서 예외를 포착하여 원래 실행 스레드가 읽고 작업 할 수있는 변수에 기록 할 수 있습니다.


답변

서로 다른 스레드간에 데이터를 공유하는 가장 간단한 방법은 shared data다음과 같습니다 (일부는 의사 코드 임).

class MyThread
{
   public string SharedData;

   public void Worker()
   {
      ...lengthy action, infinite loop, etc...
      SharedData = "whatever";
      ...lengthy action...
      return;
   }
}

class Program
{
   static void Main()
   {
      MyThread m = new MyThread();
      Thread WorkerThread = new Thread(m.Worker);
      WorkerThread.Start();

      loop//or e.g. a Timer thread
      {
         f(m.SharedData);
      }
      return;
   }
}

멀티 스레딩에 대한 멋진 소개 에서이 방법에 대해 읽을 수 있습니다. 에 있지만, 저는이 O'Reilly book C# 3.0 in a nutshell책의 최신 버전과 마찬가지로 Google 도서에서도 무료로 액세스 할 수있는 형제 Albahari (2007)의 에서 이에 대해 읽기를 선호했습니다 . 또한 멋지고 간단한 예제 코드로 스레드 풀링, 전경 스레드 대 백그라운드 스레드 등을 다루기 때문입니다. (면책 조항 :이 책의 낡은 사본을 소유하고 있습니다)

WinForms 응용 프로그램을 만드는 경우에는 공유 데이터를 사용하는 것이 특히 편리합니다. WinForm 컨트롤은 스레드로부터 안전하지 않기 때문입니다. 콜백을 사용하여 작업자 스레드의 데이터를 WinForm 컨트롤로 다시 전달하면 기본 UI 스레드가 Invoke()해당 컨트롤을 스레드로부터 안전하게 만들기 위해 추악한 코드가 필요 합니다. 대신 공유 데이터와 단일 스레드 System.Windows.Forms.Timer를 사용하면 Interval0.2 초라 는 짧은 시간 에 .NET없이 작업자 스레드에서 컨트롤로 정보를 쉽게 보낼 수 있습니다 Invoke.


답변

통합 테스트 스위트에서 컨트롤이 포함 된 항목을 사용하고 싶었 기 때문에 STA 스레드를 만들어야한다는 특별한 문제가있었습니다. 내가 만든 코드는 다음과 같으며 다른 사람들이 동일한 문제를 겪을 경우를 대비하여 여기에 넣었습니다.

    public Boolean? Dance(String name) {

        // Already on an STA thread, so just go for it
        if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) return DanceSTA(name);

        // Local variable to hold the caught exception until the caller can rethrow
        Exception lException = null;

        Boolean? lResult = null;

        // A gate to hold the calling thread until the called thread is done
        var lGate = new ManualResetEvent(false);

        var lThreadStart = new ThreadStart(() => {
            try {
                lResult = DanceSTA(name);
            } catch (Exception ex) {
                lException = ex;
            }
            lGate.Set();
        });

        var lThread = new Thread(lThreadStart);
        lThread.SetApartmentState(ApartmentState.STA);
        lThread.Start();

        lGate.WaitOne();

        if (lException != null) throw lException;

        return lResult;
    }

    public Boolean? DanceSTA(String name) { ... }

이것은 코드를 그대로 붙여 넣는 것입니다. 다른 용도의 경우에는 작업이나 함수를 매개 변수로 제공하고 호출 된 메서드를 하드 코딩하는 대신 스레드에서 호출하는 것이 좋습니다.


답변