다른 스레드에서 발생하는 예외 포착


110

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

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

답변:


182

에서 .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();
        }
    }

죄송하지만 .NET 3.5를 사용하고 있다는 사실을 잊었습니다. 내 이해에 따라 작업은 4.0 일입니까?
Silverlight 학생

2
@SilverlightStudent 네, 방금 귀하의 요구 사항을 충족시키기 위해 답변을 업데이트했습니다.
oxilumin

@oxilumin : 감사합니다. 후속 질문이 하나 더 있습니다. Test () 메서드가 몇 개의 인수를 사용한다면 해당 인수에 대한 SafeExecute 메서드를 어떻게 수정합니까?
Silverlight 학생

2
@SilverlightStudent이 경우에는 대신 람다를 전달합니다 Test. 좋아요() => Test(myParameter1, myParameter2)
옥시 루민

2
@SilverlightStudent : 업데이트되었습니다.
옥시 루민

9

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


응답 해 주셔서 감사합니다. 따라서 Method1이 Class1의 일부이고 해당 클래스에 Exception 유형의 변수가있는 경우. Method2가 예외를 throw 할 때마다 Class1에서도 해당 예외 변수를 설정합니다. 공정한 디자인처럼 들리나요? 이 시나리오를 처리하는 모범 사례가 있습니까?
Silverlight 학생

맞습니다. 예외를 저장하고 나중에 액세스하면됩니다. 나중에 실행되는 메서드 (특히 Method2가 완료 될 때의 콜백)가 예외를 발생시킨 것처럼 다시 throw하는 것은 드문 일이 아니지만 이는 실제로 원하는 것에 달려 있습니다.
ermau

0

서로 다른 스레드간에 데이터를 공유하는 가장 간단한 방법은 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.


0

통합 테스트 스위트에서 컨트롤이 포함 된 항목을 사용하고 싶었 기 때문에 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) { ... }

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

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.