파이썬의 호출자 스레드에서 스레드의 예외를 잡아라.


208

저는 파이썬과 멀티 스레드 프로그래밍에 익숙하지 않습니다. 기본적으로 파일을 다른 위치로 복사하는 스크립트가 있습니다. ....스크립트가 여전히 실행 중임을 표시하기 위해 이것을 다른 스레드에 배치하고 싶습니다 .

내가 겪고있는 문제는 파일을 복사 할 수 없으면 예외가 발생한다는 것입니다. 메인 스레드에서 실행 중이면 괜찮습니다. 그러나 다음 코드가 작동하지 않습니다.

try:
    threadClass = TheThread(param1, param2, etc.)
    threadClass.start()   ##### **Exception takes place here**
except:
    print "Caught an exception"

스레드 클래스 자체에서 예외를 다시 throw하려고 시도했지만 작동하지 않습니다. 나는 여기에있는 사람들이 비슷한 질문을하는 것을 보았지만 모두 내가하려고하는 것보다 더 구체적인 것을하고있는 것 같습니다 (그리고 제공된 솔루션을 이해하지 못합니다). 나는 사람들이의 사용법을 언급하는 것을 보았지만 sys.exc_info()그것을 어디서 어떻게 사용하는지 모른다.

모든 도움을 주셔서 감사합니다!

편집 : 스레드 클래스의 코드는 다음과 같습니다.

class TheThread(threading.Thread):
    def __init__(self, sourceFolder, destFolder):
        threading.Thread.__init__(self)
        self.sourceFolder = sourceFolder
        self.destFolder = destFolder

    def run(self):
        try:
           shul.copytree(self.sourceFolder, self.destFolder)
        except:
           raise

내부에서 일어나는 일에 대해 더 많은 통찰력을 제공 할 수 있습니까 TheThread? 아마도 코드 샘플?
자탄 교

확실한. 세부 사항을 포함하도록 위의 답변을 수정하겠습니다.
Phanto

1
메인 스레드가 작업을 수행하는 비트이고 진행 표시기가 생성 된 스레드에 있도록 라운드 전환을 고려 했습니까?
Dan Head

1
Dan Head, 먼저 "..."기능을 생성 한 다음 복사 기능을 실행하는 메인 스레드를 참조하고 있습니까? 그것은 작동하고 예외 문제를 피할 수 있습니다. 그러나 여전히 파이썬에서 올바르게 스레드하는 방법을 배우고 싶습니다.
Phanto

답변:


114

문제는 바로 thread_obj.start()돌아 오는 것입니다. 생성 한 자식 스레드는 자체 스택과 함께 자체 컨텍스트에서 실행됩니다. 하위 스레드의 컨텍스트에서 발생하는 예외는 자체 스택에 있습니다. 이 정보를 상위 스레드에 전달하기 위해 지금 생각할 수있는 한 가지 방법은 일종의 메시지 전달을 사용하는 것입니다.

크기에 대해 이것을보십시오 :

import sys
import threading
import Queue


class ExcThread(threading.Thread):

    def __init__(self, bucket):
        threading.Thread.__init__(self)
        self.bucket = bucket

    def run(self):
        try:
            raise Exception('An error occured here.')
        except Exception:
            self.bucket.put(sys.exc_info())


def main():
    bucket = Queue.Queue()
    thread_obj = ExcThread(bucket)
    thread_obj.start()

    while True:
        try:
            exc = bucket.get(block=False)
        except Queue.Empty:
            pass
        else:
            exc_type, exc_obj, exc_trace = exc
            # deal with the exception
            print exc_type, exc_obj
            print exc_trace

        thread_obj.join(0.1)
        if thread_obj.isAlive():
            continue
        else:
            break


if __name__ == '__main__':
    main()

5
이 추악한 while 루프 대신 스레드를 결합하지 않는 이유는 무엇입니까? 참고 항목 multiprocessing: 해당 gist.github.com/2311116
schlamar

1
@Lasse 답변을 기반으로 EventHook 패턴 stackoverflow.com/questions/1092531/event-system-in-python/…을 사용하지 않는 이유는 무엇 입니까? 루프가 아닌?
Andre Miras

1
대기열은 전체 대기열을 원하지 않는 한 오류를 다시 전달하기에 가장 좋은 수단이 아닙니다. 훨씬 나은 구문은 threading.Event ()
Muposat

1
이것은 나에게 안전하지 않은 것 같습니다. 스레드가 후 예외 오른쪽 제기하면 어떻게됩니까 bucket.get()인상을 Queue.Empty? 그런 다음 스레드 join(0.1)가 완료되고 isAlive() is False예외가 누락됩니다.
Steve

1
Queue이 간단한 경우에는 불필요합니다. 예외 직후에 완료되는 ExcThread것을 확인하는 run()한 (이 간단한 예에서 와 같이) 예외 정보를 속성으로 저장할 수 있습니다 . 그런 다음 (또는 동안) 예외를 다시 발생 t.join()시킵니다. join()스레드가 완료되었는지 확인 하기 때문에 동기화 문제가 없습니다 . 아래의 Rok Strniša의 답변을 참조하십시오 stackoverflow.com/a/12223550/126362
ejm

42

concurrent.futures모듈을 사용하면 별도의 스레드 (또는 프로세스)에서 작업하고 결과 예외를 처리 할 수 ​​있습니다.

import concurrent.futures
import shutil

def copytree_with_dots(src_path, dst_path):
    with concurrent.futures.ThreadPoolExecutor(max_workers=1) as executor:
        # Execute the copy on a separate thread,
        # creating a future object to track progress.
        future = executor.submit(shutil.copytree, src_path, dst_path)

        while future.running():
            # Print pretty dots here.
            pass

        # Return the value returned by shutil.copytree(), None.
        # Raise any exceptions raised during the copy process.
        return future.result()

concurrent.futuresPython 3.2에 포함되어 있으며 이전 버전 의 백 포트 futures모듈 로 제공됩니다 .


5
이것은 OP가 요구 한 것을 정확하게하지는 않지만, 내가 필요한 힌트입니다. 감사합니다.
Mad Physicist

2
와 함께 concurrent.futures.as_completed예외가 발생하면 즉시 알림을받을 수 있습니다. stackoverflow.com/questions/2829329/…
Ciro Santilli 郝海东 冠状 病 六四 事 冠状 病 六 法轮功

1
이 코드는 메인 스레드를 차단하고 있습니다. 이 작업을 어떻게 비동기식으로 수행합니까?
Nikolay Shindarov

40

이 질문에 대해 정말 이상하게 복잡한 답변이 많이 있습니다. 이것이 나에게 대부분의 것들에 충분 해 보이기 때문에 이것을 지나치게 단순화하고 있습니까?

from threading import Thread

class PropagatingThread(Thread):
    def run(self):
        self.exc = None
        try:
            if hasattr(self, '_Thread__target'):
                # Thread uses name mangling prior to Python 3.
                self.ret = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
            else:
                self.ret = self._target(*self._args, **self._kwargs)
        except BaseException as e:
            self.exc = e

    def join(self):
        super(PropagatingThread, self).join()
        if self.exc:
            raise self.exc
        return self.ret

확실하게 하나 또는 다른 버전의 Python에서만 실행된다고 확신하는 경우 run()메소드를 맹 글링 된 버전으로 줄이십시오 (3 이전의 Python 버전에서만 실행되는 경우). 깨끗한 버전 (3으로 시작하는 Python 버전에서만 실행되는 경우).

사용법 예 :

def f(*args, **kwargs):
    print(args)
    print(kwargs)
    raise Exception('I suck at this')

t = PropagatingThread(target=f, args=(5,), kwargs={'hello':'world'})
t.start()
t.join()

그리고 가입 할 때 다른 스레드에서 예외가 발생하는 것을 볼 수 있습니다.

sixPython 3 만 사용 하거나 사용 하는 경우 예외가 다시 발생했을 때 얻는 스택 추적 정보를 향상시킬 수 있습니다. 조인 시점의 스택 대신 새 외부 예외에서 내부 예외를 래핑하고 두 스택 추적을 모두 얻을 수 있습니다.

six.raise_from(RuntimeError('Exception in thread'),self.exc)

또는

raise RuntimeError('Exception in thread') from self.exc

1
이 답변이 더 인기가없는 이유는 확실하지 않습니다. 간단한 전파도 수행하지만 클래스를 확장하고 재정의해야하는 다른 항목도 있습니다. 이것은 많은 사람들이 기대하는 것을 수행하며 Thread에서 ProagatingThread로 변경하면됩니다. 그리고 4 개의 공백 탭이 있으므로 복사 / 붙여 넣기가 사소한 것입니다 :-) ... 내가 제안한 유일한 개선 사항은 six.raise_from ()을 사용하는 것입니다. reraise의 사이트.
aggieNick02

대단히 감사합니다. 매우 간단한 해결책.
sonulohani

내 문제는 여러 개의 자식 스레드가 있다는 것입니다. 결합은 순서대로 실행되며 나중에 결합 된 스레드에서 예외가 발생할 수 있습니다. 내 문제에 대한 간단한 해결책이 있습니까? 조인을 동시에 실행 하시겠습니까?
chuan

고마워, 그것은 완벽하게 작동합니다! 왜 파이썬으로 직접 처리되지 않는지 잘 모르겠습니다…
GG.

이것은 가장 유용한 답변으로 정의되며,이 sulution은 다른 것보다 훨씬 일반적이지만 간단합니다. 프로젝트에서 사용합니다!
Konstantin Sekeresh

30

다른 스레드에서 발생한 예외를 직접 잡을 수는 없지만이 기능과 매우 유사한 것을 투명하게 얻는 코드는 다음과 같습니다. 자식 스레드는 ExThread클래스 대신 서브 클래스를 작성 threading.Thread해야 하며 스레드는 작업 완료를 기다릴 때가 child_thread.join_with_exception()아니라 메소드를 호출해야합니다 child_thread.join().

이 구현의 기술적 세부 사항 : 자식 스레드가 예외를 throw하면 예외를 통해 부모에게 전달되고 Queue부모 스레드에서 다시 throw됩니다. 이 접근 방식에는 바쁘지 않습니다.

#!/usr/bin/env python

import sys
import threading
import Queue

class ExThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.__status_queue = Queue.Queue()

    def run_with_exception(self):
        """This method should be overriden."""
        raise NotImplementedError

    def run(self):
        """This method should NOT be overriden."""
        try:
            self.run_with_exception()
        except BaseException:
            self.__status_queue.put(sys.exc_info())
        self.__status_queue.put(None)

    def wait_for_exc_info(self):
        return self.__status_queue.get()

    def join_with_exception(self):
        ex_info = self.wait_for_exc_info()
        if ex_info is None:
            return
        else:
            raise ex_info[1]

class MyException(Exception):
    pass

class MyThread(ExThread):
    def __init__(self):
        ExThread.__init__(self)

    def run_with_exception(self):
        thread_name = threading.current_thread().name
        raise MyException("An error in thread '{}'.".format(thread_name))

def main():
    t = MyThread()
    t.start()
    try:
        t.join_with_exception()
    except MyException as ex:
        thread_name = threading.current_thread().name
        print "Caught a MyException in thread '{}': {}".format(thread_name, ex)

if __name__ == '__main__':
    main()

1
당신은 잡으려면하지 않을까요 BaseException하지 Exception? 당신이하고있는 일은 예외 Thread를 다른 것으로 전파하는 것입니다 . 현재 IE KeyboardInterrupt는 백그라운드 스레드에서 발생하면 자동으로 무시됩니다.
ArtOfWarfare

join_with_exception죽은 스레드에서 두 번째로 호출되면 무기한 정지됩니다. 수정 : github.com/fraserharris/threading-extensions/blob/master/…
프레이저 해리스

나는 필요하지 않다고 생각 Queue한다. @Santa의 답변에 대한 내 의견을 참조하십시오. stackoverflow.com/a/12223550/126362
ejm

22

스레드에서 예외가 발생하면 가장 좋은 방법은 호출하는 동안 호출자 스레드에서 예외를 발생시키는 것입니다 join. sys.exc_info()함수를 사용하여 현재 처리중인 예외에 대한 정보를 얻을 수 있습니다 . 이 정보 join는 호출 될 때까지 스레드 개체의 속성으로 간단히 저장 될 수 있으며이 시점에서 다시 올릴 수 있습니다.

유의 Queue.Queue(다른 응답에서 제안 된 바와 같은) 실이 간단한 경우에서 불필요 하게는 1 예외 발생오른쪽 예외 발생을 완료 한 후 . 스레드가 완료 될 때까지 기다리면 경쟁 조건을 피할 수 있습니다.

예를 들어, 확장 ExcThread(아래) excRun대신 재정의 ( 아래 run)합니다.

파이썬 2.x :

import threading

class ExcThread(threading.Thread):
  def excRun(self):
    pass

  def run(self):
    self.exc = None
    try:
      # Possibly throws an exception
      self.excRun()
    except:
      import sys
      self.exc = sys.exc_info()
      # Save details of the exception thrown but don't rethrow,
      # just complete the function

  def join(self):
    threading.Thread.join(self)
    if self.exc:
      msg = "Thread '%s' threw an exception: %s" % (self.getName(), self.exc[1])
      new_exc = Exception(msg)
      raise new_exc.__class__, new_exc, self.exc[2]

파이썬 3.x :

3 인수 형식 raise은 Python 3에서 사라 졌으므로 마지막 줄을 다음과 같이 변경하십시오.

raise new_exc.with_traceback(self.exc[2])

2
super (ExcThread, self) .join () 대신 threading.Thread.join (self)을 사용하는 이유는 무엇입니까?
Richard Möhn

9

concurrent.futures.as_completed

https://docs.python.org/3.7/library/concurrent.futures.html#concurrent.futures.as_completed

다음 해결책 :

  • 예외가 호출되면 즉시 메인 스레드로 돌아갑니다.
  • 추가 사용자 정의 클래스가 필요하지 않으므로 다음이 필요하지 않습니다.
    • 명백한 Queue
    • 작업 스레드 주위에 예외를 추가하려면

출처:

#!/usr/bin/env python3

import concurrent.futures
import time

def func_that_raises(do_raise):
    for i in range(3):
        print(i)
        time.sleep(0.1)
    if do_raise:
        raise Exception()
    for i in range(3):
        print(i)
        time.sleep(0.1)

with concurrent.futures.ThreadPoolExecutor(max_workers=2) as executor:
    futures = []
    futures.append(executor.submit(func_that_raises, False))
    futures.append(executor.submit(func_that_raises, True))
    for future in concurrent.futures.as_completed(futures):
        print(repr(future.exception()))

가능한 출력 :

0
0
1
1
2
2
0
Exception()
1
2
None

불행히도 다른 사람이 실패하면 선물을 죽여서 다른 사람을 취소하는 것은 불가능합니다.

당신이 같은 것을하면 :

for future in concurrent.futures.as_completed(futures):
    if future.exception() is not None:
        raise future.exception()

그런 다음 with잡아서 두 번째 스레드가 끝날 때까지 기다렸다가 계속합니다. 다음은 비슷하게 동작합니다.

for future in concurrent.futures.as_completed(futures):
    future.result()

때문에 future.result()예외를 재 레이즈 하나가 발생한 경우.

전체 파이썬 프로세스를 종료하려면을 피할 수 os._exit(0)있지만 리 팩터가 필요할 수 있습니다.

완벽한 예외 의미론을 갖춘 커스텀 클래스

: 나는 나 자신을위한 완벽한 인터페이스를 코딩 결국 한 번에 실행 스레드의 최대 수를 제한 할 수있는 올바른 방법? "오류 처리가 포함 된 대기열 예"섹션. 이 수업은 편리하고 제출 및 결과 / 오류 처리를 완전히 제어하는 ​​것을 목표로합니다.

Python 3.6.7, Ubuntu 18.04에서 테스트되었습니다.


4

이것은 불쾌한 작은 문제 였고 솔루션을 넣고 싶습니다. 내가 찾은 다른 솔루션 (예 : async.io)은 유망 해 보였지만 약간의 블랙 박스를 제시했습니다. 큐 / 이벤트 루프 접근 방식은 특정 구현에 연결됩니다. 그러나 동시 선물 소스 코드는 약 1000 줄에 불과하며 이해하기 쉽습니다 . 많은 문제없이 임시 작업자 스레드를 만들고 주 스레드에서 예외를 포착 할 수 있도록 문제를 쉽게 해결할 수있었습니다.

내 솔루션은 동시 선물 API 및 스레딩 API를 사용합니다. 스레드와 미래를 모두 제공하는 작업자를 만들 수 있습니다. 이렇게하면 스레드를 결합하여 결과를 기다릴 수 있습니다.

worker = Worker(test)
thread = worker.start()
thread.join()
print(worker.future.result())

또는 완료되면 작업자가 콜백을 보내도록 할 수 있습니다.

worker = Worker(test)
thread = worker.start(lambda x: print('callback', x))

... 또는 이벤트가 완료 될 때까지 반복 할 수 있습니다.

worker = Worker(test)
thread = worker.start()

while True:
    print("waiting")
    if worker.future.done():
        exc = worker.future.exception()
        print('exception?', exc)
        result = worker.future.result()
        print('result', result)           
        break
    time.sleep(0.25)

코드는 다음과 같습니다.

from concurrent.futures import Future
import threading
import time

class Worker(object):
    def __init__(self, fn, args=()):
        self.future = Future()
        self._fn = fn
        self._args = args

    def start(self, cb=None):
        self._cb = cb
        self.future.set_running_or_notify_cancel()
        thread = threading.Thread(target=self.run, args=())
        thread.daemon = True #this will continue thread execution after the main thread runs out of code - you can still ctrl + c or kill the process
        thread.start()
        return thread

    def run(self):
        try:
            self.future.set_result(self._fn(*self._args))
        except BaseException as e:
            self.future.set_exception(e)

        if(self._cb):
            self._cb(self.future.result())

... 및 테스트 기능 :

def test(*args):
    print('args are', args)
    time.sleep(2)
    raise Exception('foo')

2

스레딩의 전문가로서 Mateusz Kobos의 코드를 구현하는 방법을 이해하는 데 오랜 시간이 걸렸습니다. 다음은 사용법을 이해하는 데 도움이되는 명확한 버전입니다.

#!/usr/bin/env python

import sys
import threading
import Queue

class ExThread(threading.Thread):
    def __init__(self):
        threading.Thread.__init__(self)
        self.__status_queue = Queue.Queue()

    def run_with_exception(self):
        """This method should be overriden."""
        raise NotImplementedError

    def run(self):
        """This method should NOT be overriden."""
        try:
            self.run_with_exception()
        except Exception:
            self.__status_queue.put(sys.exc_info())
        self.__status_queue.put(None)

    def wait_for_exc_info(self):
        return self.__status_queue.get()

    def join_with_exception(self):
        ex_info = self.wait_for_exc_info()
        if ex_info is None:
            return
        else:
            raise ex_info[1]

class MyException(Exception):
    pass

class MyThread(ExThread):
    def __init__(self):
        ExThread.__init__(self)

    # This overrides the "run_with_exception" from class "ExThread"
    # Note, this is where the actual thread to be run lives. The thread
    # to be run could also call a method or be passed in as an object
    def run_with_exception(self):
        # Code will function until the int
        print "sleeping 5 seconds"
        import time
        for i in 1, 2, 3, 4, 5:
            print i
            time.sleep(1) 
        # Thread should break here
        int("str")
# I'm honestly not sure why these appear here? So, I removed them. 
# Perhaps Mateusz can clarify?        
#         thread_name = threading.current_thread().name
#         raise MyException("An error in thread '{}'.".format(thread_name))

if __name__ == '__main__':
    # The code lives in MyThread in this example. So creating the MyThread 
    # object set the code to be run (but does not start it yet)
    t = MyThread()
    # This actually starts the thread
    t.start()
    print
    print ("Notice 't.start()' is considered to have completed, although" 
           " the countdown continues in its new thread. So you code "
           "can tinue into new processing.")
    # Now that the thread is running, the join allows for monitoring of it
    try:
        t.join_with_exception()
    # should be able to be replace "Exception" with specific error (untested)
    except Exception, e: 
        print
        print "Exceptioon was caught and control passed back to the main thread"
        print "Do some handling here...or raise a custom exception "
        thread_name = threading.current_thread().name
        e = ("Caught a MyException in thread: '" + 
             str(thread_name) + 
             "' [" + str(e) + "]")
        raise Exception(e) # Or custom class of exception, such as MyException

2

Queue, sys 등이없고 신호에 대한 일부 리스너가없는 RickardSjogren과 유사한 방식 : except 블록에 해당하는 예외 핸들러를 직접 실행하십시오.

#!/usr/bin/env python3

import threading

class ExceptionThread(threading.Thread):

    def __init__(self, callback=None, *args, **kwargs):
        """
        Redirect exceptions of thread to an exception handler.

        :param callback: function to handle occured exception
        :type callback: function(thread, exception)
        :param args: arguments for threading.Thread()
        :type args: tuple
        :param kwargs: keyword arguments for threading.Thread()
        :type kwargs: dict
        """
        self._callback = callback
        super().__init__(*args, **kwargs)

    def run(self):
        try:
            if self._target:
                self._target(*self._args, **self._kwargs)
        except BaseException as e:
            if self._callback is None:
                raise e
            else:
                self._callback(self, e)
        finally:
            # Avoid a refcycle if the thread is running a function with
            # an argument that has a member that points to the thread.
            del self._target, self._args, self._kwargs, self._callback

self._callback과 run ()의 except 블록 만 일반 스레딩에 추가됩니다.


2

나는 여기서 파티에 조금 늦었다는 것을 알고 있지만 매우 비슷한 문제가 있었지만 tkinter를 GUI로 사용하는 것이 포함되었으며 mainloop로 인해 .join ()에 의존하는 솔루션을 사용할 수 없었습니다. 따라서 나는 원래 질문의 편집에서 주어진 해결책을 채택했지만 다른 사람들이 더 쉽게 이해할 수 있도록보다 일반적으로 만들었습니다.

다음은 새로운 스레드 클래스입니다.

import threading
import traceback
import logging


class ExceptionThread(threading.Thread):
    def __init__(self, *args, **kwargs):
        threading.Thread.__init__(self, *args, **kwargs)

    def run(self):
        try:
            if self._target:
                self._target(*self._args, **self._kwargs)
        except Exception:
            logging.error(traceback.format_exc())


def test_function_1(input):
    raise IndexError(input)


if __name__ == "__main__":
    input = 'useful'

    t1 = ExceptionThread(target=test_function_1, args=[input])
    t1.start()

물론 예외를 인쇄하거나 콘솔로 출력하는 것과 같은 다른 방법으로 예외를 처리하도록 할 수 있습니다.

따라서 특별한 수정없이 Thread 클래스와 똑같이 ExceptionThread 클래스를 사용할 수 있습니다.


1

내가 좋아하는 한 가지 방법은 관찰자 패턴을 기반으로합니다 . 스레드가 리스너에게 예외를 발생시키는 데 사용하는 신호 클래스를 정의합니다. 스레드에서 값을 반환하는 데에도 사용할 수 있습니다. 예:

import threading

class Signal:
    def __init__(self):
        self._subscribers = list()

    def emit(self, *args, **kwargs):
        for func in self._subscribers:
            func(*args, **kwargs)

    def connect(self, func):
        self._subscribers.append(func)

    def disconnect(self, func):
        try:
            self._subscribers.remove(func)
        except ValueError:
            raise ValueError('Function {0} not removed from {1}'.format(func, self))


class WorkerThread(threading.Thread):

    def __init__(self, *args, **kwargs):
        super(WorkerThread, self).__init__(*args, **kwargs)
        self.Exception = Signal()
        self.Result = Signal()

    def run(self):
        if self._Thread__target is not None:
            try:
                self._return_value = self._Thread__target(*self._Thread__args, **self._Thread__kwargs)
            except Exception as e:
                self.Exception.emit(e)
            else:
                self.Result.emit(self._return_value)

if __name__ == '__main__':
    import time

    def handle_exception(exc):
        print exc.message

    def handle_result(res):
        print res

    def a():
        time.sleep(1)
        raise IOError('a failed')

    def b():
        time.sleep(2)
        return 'b returns'

    t = WorkerThread(target=a)
    t2 = WorkerThread(target=b)
    t.Exception.connect(handle_exception)
    t2.Result.connect(handle_result)
    t.start()
    t2.start()

    print 'Threads started'

    t.join()
    t2.join()
    print 'Done'

나는 이것이 완전히 안전한 방법이라고 주장하기 위해 스레드를 사용한 경험이 충분하지 않습니다. 그러나 그것은 나를 위해 일했고 유연성을 좋아합니다.


join () 후에 연결을 끊습니까?
ealeon

나는하지 않지만 좋은 아이디어 일 것이라고 생각하므로 사용하지 않는 물건에 대한 언급이 없습니다.
RickardSjogren

"handle_exception"이 여전히 자식 스레드의 일부라는 것을 알았습니다. 스레드 호출자에게 전달해야합니다
ealeon

1

알몸의 예외를 사용하는 것은 일반적으로 거래보다 많은 것을 잡기 때문에 좋은 습관이 아닙니다.

except처리하려는 예외 만 잡도록 수정하는 것이 좋습니다 . TheThread외부에서 인스턴스화 할 때 원하는 효과가 있다고 생각하지 않습니다.try 예외가 발생하면 할당이 발생하지 .

대신 다음과 같이 경고하고 계속 진행할 수 있습니다.

def run(self):
    try:
       shul.copytree(self.sourceFolder, self.destFolder)
    except OSError, err:
       print err

그런 다음 해당 예외가 발생하면 처리 할 수 ​​있습니다. 그런 다음 외부 try가에서 예외를 포착 TheThread하면 이미 처리 한 예외 가 아니라는 것을 알고 프로세스 흐름을 분리하는 데 도움이됩니다.


1
글쎄, 그 스레드에 오류가 있다면, 전체 프로그램이 문제가 있음을 사용자에게 알리고 정상적으로 종료되기를 바랍니다. 따라서 메인 스레드가 모든 예외를 포착하고 처리하기를 원합니다. 그러나 TheThread가 예외를 던지면 메인 스레드의 시도 / 제외가 여전히 예외를 포착하지 못하는 문제가 여전히 존재합니다. 스레드가 예외를 감지하고 작업이 실패했음을 나타내는 false를 반환하도록 할 수 있습니다. 그것은 동일한 원하는 결과를 얻을 수 있지만 여전히 하위 스레드 예외를 올바르게 잡는 방법을 알고 싶습니다.
Phanto

1

스레드의 예외를 포착하고 호출자 메소드와 다시 통신하는 간단한 방법은 사전 또는 목록을 worker메소드 에 전달하는 것입니다.

예 (사전을 작업자 메서드로 전달) :

import threading

def my_method(throw_me):
    raise Exception(throw_me)

def worker(shared_obj, *args, **kwargs):
    try:
        shared_obj['target'](*args, **kwargs)
    except Exception as err:
        shared_obj['err'] = err

shared_obj = {'err':'', 'target': my_method}
throw_me = "Test"

th = threading.Thread(target=worker, args=(shared_obj, throw_me), kwargs={})
th.start()
th.join()

if shared_obj['err']:
    print(">>%s" % shared_obj['err'])

1

예외 스토리지로 스레드를 랩핑하십시오.

import threading
import sys
class ExcThread(threading.Thread):

    def __init__(self, target, args = None):
        self.args = args if args else []
        self.target = target
        self.exc = None
        threading.Thread.__init__(self)

    def run(self):
        try:
            self.target(*self.args)
            raise Exception('An error occured here.')
        except Exception:
            self.exc=sys.exc_info()

def main():
    def hello(name):
        print(!"Hello, {name}!")
    thread_obj = ExcThread(target=hello, args=("Jack"))
    thread_obj.start()

    thread_obj.join()
    exc = thread_obj.exc
    if exc:
        exc_type, exc_obj, exc_trace = exc
        print(exc_type, ':',exc_obj, ":", exc_trace)

main()

0

pygolang가 제공 sync.WorkGroup , 특히, 메인 스레드에 양산 작업자 스레드에서 예외를 전파합니다. 예를 들면 다음과 같습니다.

#!/usr/bin/env python
"""This program demostrates how with sync.WorkGroup an exception raised in
spawned thread is propagated into main thread which spawned the worker."""

from __future__ import print_function
from golang import sync, context

def T1(ctx, *argv):
    print('T1: run ... %r' % (argv,))
    raise RuntimeError('T1: problem')

def T2(ctx):
    print('T2: ran ok')

def main():
    wg = sync.WorkGroup(context.background())
    wg.go(T1, [1,2,3])
    wg.go(T2)

    try:
        wg.wait()
    except Exception as e:
        print('Tmain: caught exception: %r\n' %e)
        # reraising to see full traceback
        raise

if __name__ == '__main__':
    main()

실행할 때 다음을 제공합니다.

T1: run ... ([1, 2, 3],)
T2: ran ok
Tmain: caught exception: RuntimeError('T1: problem',)

Traceback (most recent call last):
  File "./x.py", line 28, in <module>
    main()
  File "./x.py", line 21, in main
    wg.wait()
  File "golang/_sync.pyx", line 198, in golang._sync.PyWorkGroup.wait
    pyerr_reraise(pyerr)
  File "golang/_sync.pyx", line 178, in golang._sync.PyWorkGroup.go.pyrunf
    f(pywg._pyctx, *argv, **kw)
  File "./x.py", line 10, in T1
    raise RuntimeError('T1: problem')
RuntimeError: T1: problem

질문의 원래 코드는 다음과 같습니다.

    wg = sync.WorkGroup(context.background())

    def _(ctx):
        shul.copytree(sourceFolder, destFolder)
    wg.go(_)

    # waits for spawned worker to complete and, on error, reraises
    # its exception on the main thread.
    wg.wait()
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.