Вилучіть виняток потоку в потоці виклику в Python


207

Я дуже новачок у програмі Python та багатопотоковому програмуванні взагалі. В основному, у мене є сценарій, який буде копіювати файли в інше місце. Я хотів би, щоб це було розміщено в іншому потоці, щоб я міг вивести, ....щоб вказати, що сценарій все ще працює.

Проблема, яка у мене є, полягає в тому, що якщо файли неможливо скопіювати, це призведе до виключення. Це нормально, якщо працює в основному потоці; однак, мати такий код не працює:

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

У самому класі потоку я спробував перекинути виняток, але це не виходить. Я бачив, як люди тут задають подібні запитання, але всі вони, здається, роблять щось більш конкретне, ніж те, що я намагаюся зробити (і я не зовсім розумію пропоновані рішення). Я бачив, як люди згадують про використання sys.exc_info(), проте не знаю, де і як ним користуватися.

Вся допомога дуже вдячна!

EDIT: Код для класу потоку знаходиться нижче:

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
Чи обмірковували ви перемикання його навколо, щоб основна тема була бітом, який виконує завдання, а індикатор прогресу знаходиться в породженій нитці?
Дан Голова

1
Ден Хед, ви маєте на увазі основний потік спочатку нерестуючи функцію "...", а потім запустивши функцію копіювання? Це може спрацювати і уникнути проблеми виключення. Але я все ж хотів би навчитися правильно прошивати нитки в python.
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
Чому б не приєднатись до потоку замість цього потворного циклу? Дивіться multiprocessingеквівалент: gist.github.com/2311116
schlamar

1
Чому не використовувати шаблон EventHook stackoverflow.com/questions/1092531/event-system-in-python / ... заснований на @Lasse відповідь? Замість петлевої речі?
Андре Мірас

1
Черга - не найкращий транспортний засіб для повідомлення про помилку, якщо ви не хочете мати повну чергу з них. Набагато краща конструкція - різьблення.
Евент

1
Це здається мені небезпечним. Що відбувається, коли нитка піднімає виняток відразу після bucket.get()підвищення Queue.Empty? Тоді нитка join(0.1)завершиться і isAlive() is False, і ви пропустите свій виняток.
Стів

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.futuresвходить до Python 3.2 та доступний як підтримуваний futuresмодуль для більш ранніх версій.


5
Хоча це не робить саме те, що просив ОП, це саме той підказок, який мені потрібен. Дякую.
Божевільний фізик

2
І concurrent.futures.as_completed, ви можете отримати негайно повідомлені підняті виключення: stackoverflow.com/questions/2829329 / ...
Чіро Сантіллі郝海东冠状病六四事件法轮功

1
Цей код блокує основну нитку. Як це робити асинхронно?
Микола Шиндаров

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()метод до просто розробленої версії (якщо ви будете працювати лише на версіях Python до 3-х), або просто чиста версія (якщо ви будете працювати лише на версіях Python, починаючи з 3).

Приклад використання:

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

І ви побачите виняток, піднятий в іншому потоці, коли ви приєднаєтесь.

Якщо ви використовуєте sixабо лише на Python 3, ви можете покращити інформацію про стеження стека, яку ви отримаєте, коли виняток повторно збільшується. Замість того, щоб тільки стек у точці з'єднання, ви можете обернути внутрішній виняток у новий зовнішній виняток і отримати обидва сліди стека за допомогою

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

або

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

1
Я не впевнений, чому ця відповідь теж не є популярною. Є й інші, які теж просто розповсюджують, але вимагають розширення класу та переосмислення. Цей просто робить те, що багато хто очікував, і вимагає лише переходу від теми до потокової програми. І 4 пробіли, тому моя копія / вставка була тривіальною :-) ... Єдине поліпшення, яке я б запропонував, - це використання Six.raise_from (), щоб ви отримали гарний вкладений набір стежок стека, а не просто стек для сайт ререйзу.
aggieNick02

Велике спасибі. Дуже просте рішення.
sonulohani

Моя проблема полягає в тому, що у мене є кілька дочірніх ниток. З'єднання виконуються послідовно, і виняток може бути піднятий з пізніше з'єднаних потоків. Чи є просте рішення моєї проблеми? виконувати з'єднання одночасно?
чуан

Дякую, це прекрасно працює! Не впевнений, чому це не обробляє безпосередньо пітон тхо ...
GG.

Це визначення найбільш корисної відповіді, це сулюція набагато більш загальна, ніж інші, але просте. Буде використовувати його в проекті!
Костянтин Секереш

30

Хоча неможливо безпосередньо зловити виняток, викинутий в інший потік, ось код, щоб досить прозоро отримати щось дуже близьке до цієї функціональності. Ваша дочірня нитка повинна мати підклас ExThreadзамість класу, а threading.Threadбатьківська нитка повинна викликати child_thread.join_with_exception()метод замість того, child_thread.join()щоб чекати, коли нитка закінчить свою роботу.

Технічні деталі цієї реалізації: коли дочірній потік кидає виняток, він передається батьківському через a Queueі знову кидається в батьківський потік. Зауважте, що в цьому підході немає зайнятого очікування.

#!/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, a KeyboardInterrupt, буде мовчки ігноруватися, якби він був піднятий у фонову нитку.
ArtOfWarfare

join_with_exceptionвисить нескінченно, якщо дзвонить вдруге на мертву нитку. Виправити: github.com/fraserharris/threading-extensions/blob/master/…
Фрейзер Гарріс

Я не вважаю Queueза потрібне; дивіться мій коментар до відповіді @ Санта. Ви можете спростити його вниз до чого - щось на зразок відповіді ROK Strniša в поле нижче stackoverflow.com/a/12223550/126362
EJM

22

Якщо виключення відбувається в потоці, найкращий спосіб, щоб знову підняти його в потоці абонента під час join. Ви можете отримати інформацію про виняток, який зараз обробляється за допомогою sys.exc_info()функції. Ця інформація може просто зберігатися як властивість об’єкта потоку, поки не joinбуде викликано, і в цей момент її можна повторно підняти.

Зауважте, що Queue.Queue(як це пропонується в інших відповідях) не потрібно в цьому простому випадку, коли нитка видає максимум 1 виняток і завершується відразу після викидання виключення . Ми уникаємо перегонових умов, просто чекаючи завершення теми.

Наприклад, розширити ExcThread(внизу), змінити excRun(замість 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:

У raisePython 3 відсутня форма аргументу 3, тому змініть останній рядок на:

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

2
Чому ви використовуєте threading.Thread.join (self) замість super (ExcThread, self) .join ()?
Річард Мен

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()повторно збільшує виняток, якщо таке сталося.

Якщо ви хочете залишити весь процес Python, ви можете піти звідти os._exit(0), але це, ймовірно, означає, що вам потрібен рефактор.

Спеціальний клас із досконалою семантикою винятків

Я закінчив кодування ідеального інтерфейсу для себе за адресою: Правильний спосіб обмежити максимальну кількість потоків, що працюють відразу? розділ "Приклад черги з обробкою помилок". Цей клас має бути зручним і давати вам повний контроль над поданням та обробкою результатів / помилок.

Тестовано на Python 3.6.7, Ubuntu 18.04.


4

Це була неприємна маленька проблема, і я хотів би кинути своє рішення. Деякі інші рішення, які я знайшов (наприклад, async.io), виглядали багатообіцяючими, але також подали трохи чорного поля. Підхід до черги / циклу подій певним чином пов'язує вас із певною реалізацією. Однак вихідний код ф'ючерсних контрактів становить лише 1000 рядків і простий для розуміння . Це дозволило мені легко вирішити свою проблему: створити спеціальні робочі потоки без особливих налаштувань і мати можливість виловлювати винятки в основній темі.

У моєму рішенні використовується API паралельних 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

Як ноубі до Threading, мені знадобилося багато часу, щоб зрозуміти, як реалізувати код Матеуша Кобоса (вище). Ось уточнена версія, яка допоможе зрозуміти, як ним користуватися.

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

Аналогічним чином, як RickardSjogren без черги, sys тощо, але також без сигналів слухачів: виконайте безпосередньо обробник винятків, який відповідає, крім блоку.

#!/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 () є додатковими до звичайної нарізки.


2

Я знаю, що я трохи спізнився на вечірку тут, але у мене була дуже схожа проблема, але вона включала використання tkinter як графічного інтерфейсу, і mainloop унеможливив використання будь-якого рішення, від якого залежить .join (). Тому я адаптував рішення, подане в EDIT оригінального питання, але зробив його більш загальним, щоб полегшити розуміння для інших.

Ось новий клас потоків у дії:

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

Звичайно, ви завжди можете його обробляти за винятком іншого способу із входу в систему, наприклад, роздрукувати його або вивести його на консоль.

Це дозволяє використовувати клас ExceptionThread точно так само, як і клас Thread, без особливих модифікацій.


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'

У мене недостатньо досвіду роботи з нитками, щоб стверджувати, що це абсолютно безпечний метод. Але це спрацювало для мене, і мені подобається гнучкість.


ви від'єднаєтесь після приєднання ()?
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 викине виняток, спробу /, окрім як і раніше, не вдасться впізнати. Я міг би потік виявити виняток і повернути помилкове, що вказує на те, що операція була невдалою. Це дозволило б досягти такого ж бажаного результату, але я все ж хотів би знати, як правильно вловлювати виключення підрядних потоків.
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()
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.