Capturar a exceção de um segmento no segmento de chamada em Python

208

Eu sou muito novo em Python e programação multithread em geral. Basicamente, eu tenho um script que copiará arquivos para outro local. Gostaria que isso fosse colocado em outro encadeamento, para que eu possa ....mostrar que o script ainda está em execução.

O problema que estou enfrentando é que, se os arquivos não puderem ser copiados, haverá uma exceção. Isso está ok se estiver sendo executado no thread principal; no entanto, ter o seguinte código não funciona:

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

Na própria classe do thread, tentei relançar a exceção, mas ela não funciona. Vi pessoas aqui fazendo perguntas semelhantes, mas todas parecem estar fazendo algo mais específico do que estou tentando fazer (e não entendo bem as soluções oferecidas). Vi pessoas mencionarem o uso sys.exc_info(), mas não sei onde nem como usá-lo.

Toda ajuda é bem-vinda!

EDIT: O código para a classe de thread está abaixo:

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
Phanto
fonte
Você pode fornecer mais informações sobre o que está acontecendo dentro dele TheThread? Exemplo de código, talvez?
Jathanism
Certo. Editarei minha resposta acima para incluir alguns detalhes.
Phanto 13/05
1
Você já pensou em trocá-lo para que o Thread principal seja o que faz mais coisas e o indicador de progresso esteja no Thread gerado?
Dan Head
1
Dan Head, você está se referindo ao thread principal que gera a função "..." e depois executa a função de cópia? Isso poderia funcionar e evitar o problema de exceção. Mas eu ainda gostaria de aprender como encadear corretamente em python.
Phanto 13/05

Respostas:

114

O problema é que thread_obj.start()retorna imediatamente. O thread filho que você gerou é executado em seu próprio contexto, com sua própria pilha. Qualquer exceção que ocorra existe no contexto do encadeamento filho e está em sua própria pilha. Uma maneira de pensar agora para comunicar essas informações ao encadeamento pai é usar algum tipo de passagem de mensagem, para que você possa analisar isso.

Tente isso para obter o tamanho:

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()
Santa
fonte
5
Por que não juntar o thread em vez deste loop while feio? Veja o multiprocessingequivalente: gist.github.com/2311116
schlamar 11/11/12
1
Por que não usar o padrão EventHook stackoverflow.com/questions/1092531/event-system-in-python/… com base na resposta do @Lasse? Ao invés da coisa do loop?
Andre Miras 22/01
1
A fila não é o melhor veículo para comunicar um erro de volta, a menos que você queira ter uma fila completa deles. Uma construção muito melhor é threading.Event ()
Muposat
1
Isso me parece inseguro. O que acontece quando o encadeamento gera uma exceção logo após o bucket.get()aumento Queue.Empty? Em seguida, o thread join(0.1)será concluído e isAlive() is False, e você perderá sua exceção.
Steve Steve
1
Queueé desnecessário neste caso simples - você pode simplesmente armazenar as informações da exceção como uma propriedade ExcThread, desde que seja run()concluído logo após a exceção (o que ocorre neste exemplo simples). Então você simplesmente aumenta novamente a exceção depois (ou durante) t.join(). Não há problemas de sincronização porque join()garante que o encadeamento tenha sido concluído. Veja a resposta de Rok Strniša abaixo stackoverflow.com/a/12223550/126362
ejm
42

O concurrent.futuresmódulo simplifica o trabalho em threads (ou processos) separados e manipula todas as exceções resultantes:

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.futuresestá incluído no Python 3.2 e está disponível como módulo de backportfutures para versões anteriores.

Jon-Eric
fonte
5
Embora isso não faça exatamente o que o OP pediu, é exatamente a dica de que eu precisava. Obrigado.
Mad Physicist
2
E com concurrent.futures.as_completed, você pode ser imediatamente notificado quando exceções forem geradas: stackoverflow.com/questions/2829329/…
Ciro Santilli
1
Este código está bloqueando o thread principal. Como você faz isso de forma assíncrona?
Nikolay Shindarov 28/11/19
40

Existem muitas respostas realmente estranhamente complicadas para essa pergunta. Estou simplificando demais isso, porque isso parece suficiente para a maioria das coisas para mim.

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

Se você tiver certeza de que estará rodando apenas em uma ou outra versão do Python, poderá reduzir o run()método para apenas a versão desconectada (se estiver rodando apenas nas versões do Python antes da 3) ou apenas a versão limpa (se você só estiver executando versões do Python começando com 3).

Exemplo de uso:

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()

E você verá a exceção levantada no outro segmento quando ingressar.

Se você estiver usando sixou apenas no Python 3, poderá melhorar as informações de rastreamento de pilha que obtém quando a exceção é gerada novamente. Em vez de apenas a pilha no ponto da junção, você pode agrupar a exceção interna em uma nova exceção externa e obter os dois rastreamentos de pilha com

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

ou

raise RuntimeError('Exception in thread') from self.exc
ArtOfWarfare
fonte
1
Também não sei por que essa resposta não é mais popular. Existem outros aqui que também fazem propagação simples, mas exigem a extensão de uma classe e a substituição. Este apenas faz o que muitos esperariam e requer apenas a alteração de Thread para ProagatingThread. E quatro guias de espaço para que minha cópia / pasta seja trivial :-) ... a única melhoria que eu sugiro é usar six.raise_from () para que você obtenha um bom conjunto aninhado de rastreamentos de pilha, em vez de apenas a pilha para o local do re-aumento.
aggieNick02
Muito obrigado. Solução muito simples.
sonulohani 17/03
Meu problema é que tenho vários segmentos filho. As junções são executadas em sequência e a exceção pode ser gerada nos encadeamentos ingressados ​​posteriormente. Existe uma solução simples para o meu problema? executar a junção simultaneamente?
chuan
Obrigado, isso funciona perfeitamente! Não sei por que não é tratado diretamente pelo python tho…
GG.
Esta é definitivamente a resposta mais útil, essa sulfatação é muito mais geral do que outras, ainda que simples. Vai usá-lo em um projeto!
Konstantin Sekeresh em
30

Embora não seja possível capturar diretamente uma exceção lançada em um encadeamento diferente, aqui está um código para obter de maneira bastante transparente algo muito próximo a essa funcionalidade. Seu segmento filho deve subclassificar a ExThreadclasse em vez de threading.Threade o segmento pai deve chamar o child_thread.join_with_exception()método em vez de child_thread.join()aguardar o término do trabalho.

Detalhes técnicos desta implementação: quando o encadeamento filho lança uma exceção, é passado ao pai através de um Queuee lançado novamente no encadeamento pai. Observe que não há espera ocupada nessa abordagem.

#!/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()
Mateusz Kobos
fonte
1
Você não gostaria de pegar BaseException, não Exception? Tudo o que você está fazendo é propagar a exceção de uma Threadpara outra. No momento, o IE, a KeyboardInterrupt, seria silenciosamente ignorado se fosse gerado em um encadeamento em segundo plano.
ArtOfWarfare
join_with_exceptiontrava indefinidamente se chamado pela segunda vez em um segmento morto. Correção: github.com/fraserharris/threading-extensions/blob/master/…
Fraser Harris
Eu não acho Queuenecessário; veja meu comentário na resposta do @ Santa. Você pode simplificá-lo para algo como a resposta de Rok Strniša abaixo stackoverflow.com/a/12223550/126362
ejm
22

Se ocorrer uma exceção em um encadeamento, a melhor maneira é aumentá-lo novamente durante o encadeamento do chamador join. Você pode obter informações sobre a exceção atualmente sendo tratada usando a sys.exc_info()função Essas informações podem simplesmente ser armazenadas como uma propriedade do objeto de encadeamento até joinserem chamadas e, nesse ponto, podem ser aumentadas novamente.

Observe que a Queue.Queue(como sugerido em outras respostas) não é necessário neste caso simples em que o thread lança no máximo 1 exceção e é concluído logo após o lançamento de uma exceção . Evitamos as condições da corrida, simplesmente aguardando a conclusão do segmento.

Por exemplo, estenda ExcThread(abaixo), substituindo excRun(em vez de run).

Python 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]

Python 3.x:

O formulário de 3 argumentos raisefoi removido no Python 3, então altere a última linha para:

raise new_exc.with_traceback(self.exc[2])
Rok Strniša
fonte
2
Por que você usa threading.Thread.join (self) em vez de super (ExcThread, self) .join ()?
Richard Möhn
9

concurrent.futures.as_completed

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

A seguinte solução:

  • retorna ao encadeamento principal imediatamente quando uma exceção é chamada
  • não requer classes definidas pelo usuário extra porque não precisa:
    • um explícito Queue
    • para adicionar uma exceção ao redor do seu segmento de trabalho

Fonte:

#!/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()))

Saída possível:

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

Infelizmente, não é possível matar futuros para cancelar os outros porque um falha:

Se você fizer algo como:

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

então o withpega e aguarda a conclusão do segundo encadeamento antes de continuar. O seguinte se comporta de maneira semelhante:

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

já que future.result()gera novamente a exceção, se houver uma.

Se você deseja sair de todo o processo Python, pode se safar os._exit(0), mas isso provavelmente significa que você precisa de um refator.

Classe personalizada com perfeita semântica de exceção

Acabei codificando a interface perfeita para mim em: O caminho certo para limitar o número máximo de threads em execução ao mesmo tempo? seção "Exemplo de fila com tratamento de erros". Essa classe tem como objetivo ser conveniente e fornecer controle total sobre o envio e a manipulação de resultados / erros.

Testado em Python 3.6.7, Ubuntu 18.04.

Ciro Santilli 中心 改造 中心 996ICU 六四 事件
fonte
4

Esse era um pequeno problema desagradável, e eu gostaria de lançar minha solução. Algumas outras soluções que encontrei (async.io por exemplo) pareciam promissoras, mas também apresentavam um pouco de caixa preta. A abordagem de fila / loop de eventos meio que o vincula a uma determinada implementação. O código-fonte de futuros concorrentes, no entanto, é de apenas 1000 linhas e fácil de compreender . Ele me permitiu resolver facilmente meu problema: criar threads de trabalho ad-hoc sem muita configuração e conseguir capturar exceções no thread principal.

Minha solução usa a API de futuros simultâneos e a API de segmentação. Ele permite que você crie um trabalhador que fornece o thread e o futuro. Dessa forma, você pode ingressar no segmento para aguardar o resultado:

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

... ou você pode permitir que o funcionário envie um retorno de chamada quando terminar:

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

... ou você pode fazer um loop até que o evento seja concluído:

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)

Aqui está o código:

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())

... e a função de teste:

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

Como noobie do Threading, demorei muito tempo para entender como implementar o código de Mateusz Kobos (acima). Aqui está uma versão esclarecida para ajudar a entender como usá-lo.

#!/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
BurningKrome
fonte
2

De maneira semelhante à de RickardSjogren sem Queue, sys etc., mas também sem alguns ouvintes para sinais: execute diretamente um manipulador de exceção que corresponda a um bloco de exceção.

#!/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

Somente self._callback e o bloco de exceção em run () são adicionais ao threading.Thread normal.

Chickenmarkus
fonte
2

Eu sei que estou um pouco atrasado para a festa aqui, mas estava tendo um problema muito semelhante, mas incluía o uso do tkinter como uma GUI, e o mainloop tornou impossível o uso de qualquer uma das soluções que dependem de .join (). Portanto, adaptei a solução dada no EDIT da pergunta original, mas a tornei mais geral para facilitar o entendimento para os outros.

Aqui está a nova classe de encadeamento em ação:

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()

É claro que você sempre pode lidar com a exceção de alguma outra maneira, desde o log, como imprimi-la ou enviá-la para o console.

Isso permite que você use a classe ExceptionThread exatamente como faria na classe Thread, sem nenhuma modificação especial.

Firo
fonte
1

Um método de que gosto é baseado no padrão de observador . Defino uma classe de sinal que meu segmento usa para emitir exceções para os ouvintes. Também pode ser usado para retornar valores de threads. Exemplo:

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'

Não tenho experiência suficiente em trabalhar com threads para afirmar que esse é um método completamente seguro. Mas funcionou para mim e eu gosto da flexibilidade.

RickardSjogren
fonte
você desconecta após o join ()?
ealeon
Não, mas acho que seria uma boa ideia, para que você não tenha referências a coisas não utilizadas.
RickardSjogren
notei que "handle_exception" ainda faz parte de um segmento filho. precisa maneira de passá-lo para o
responsável pela
1

Usar excertos nus não é uma boa prática, porque geralmente você pega mais do que espera.

Eu sugeriria que modificasse o exceptpara pegar APENAS a exceção que você gostaria de tratar. Não acho que aumentá-lo tenha o efeito desejado, porque quando você vai instanciar TheThreadno exterior try, se isso gera uma exceção, a tarefa nunca vai acontecer.

Em vez disso, você pode apenas alertá-lo e seguir em frente, como:

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

Então, quando essa exceção for detectada, você poderá lidar com ela lá. Então, quando o externo trycapturar uma exceção TheThread, você sabe que não será o que você já tratou e ajudará a isolar o fluxo do processo.

jathanism
fonte
1
Bem, se houver algum erro nesse segmento, quero que o programa completo informe ao usuário que houve um problema e termine normalmente. Por esse motivo, quero que o thread principal capture e manipule todas as exceções. No entanto, o problema ainda existe onde, se o TheThread lança uma exceção, a tentativa / discussão do thread principal ainda não a captura. Eu poderia fazer com que o thread detectasse a exceção e retornasse um false, indicando que a operação não teve êxito. Isso alcançaria o mesmo resultado desejado, mas eu ainda gostaria de saber como capturar corretamente uma exceção de sub-thread.
Phanto 13/05
1

Uma maneira simples de capturar a exceção do encadeamento e se comunicar com o método de chamada pode ser passando o dicionário ou uma lista para o workermétodo.

Exemplo (passando o dicionário para o método de trabalho):

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'])
rado stoyanov
fonte
1

Enrole o Thread com armazenamento de exceção.

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()
ahuigo
fonte
0

O pygolang fornece sync.WorkGroup que, em particular, propaga exceção dos threads de trabalho gerados para o thread principal. Por exemplo:

#!/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()

fornece o seguinte quando executado:

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

O código original da pergunta seria apenas:

    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()
kirr
fonte