ตรวจสอบข้อยกเว้นของเธรดในตัวเรียกเธรดใน Python


208

ฉันใหม่กับ Python และการเขียนโปรแกรมแบบมัลติเธรดโดยทั่วไป โดยทั่วไปฉันมีสคริปต์ที่จะคัดลอกไฟล์ไปยังตำแหน่งอื่น ฉันต้องการให้วางในเธรดอื่นเพื่อให้สามารถแสดงผล....เพื่อระบุว่าสคริปต์ยังคงทำงานอยู่

ปัญหาที่ฉันมีอยู่คือหากไฟล์ไม่สามารถคัดลอกได้มันจะทำให้เกิดข้อยกเว้น ไม่เป็นไรหากทำงานในเธรดหลัก อย่างไรก็ตามการมีรหัสต่อไปนี้ใช้ไม่ได้:

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

ในคลาสเธรดตัวเองฉันพยายามโยนข้อยกเว้นอีกครั้ง แต่ไม่ทำงาน ฉันเคยเห็นผู้คนที่นี่ถามคำถามที่คล้ายกัน แต่พวกเขาทั้งหมดดูเหมือนจะทำสิ่งที่เฉพาะเจาะจงมากกว่าสิ่งที่ฉันพยายามทำ (และฉันไม่ค่อยเข้าใจวิธีแก้ไขที่เสนอ) ฉันเคยเห็นคนพูดถึงการใช้งานของ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หรือไม่? ตัวอย่างโค้ดอาจจะ?
jathanism

แน่ใจ ฉันจะแก้ไขคำตอบของฉันด้านบนเพื่อรวมรายละเอียดบางอย่าง
Phanto

1
คุณได้พิจารณาการสลับมันเพื่อให้เธรดหลักเป็นบิตที่ทำสิ่งต่าง ๆ และตัวบ่งชี้ความคืบหน้าอยู่ในเธรด spawned หรือไม่
ด่านหน้า

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
ทำไมไม่เข้าร่วมเธรดแทนสิ่งนี้น่าเกลียดในขณะที่วนซ้ำ? ดูสิ่งที่multiprocessingเทียบเท่า: gist.github.com/2311116
schlamar

1
ทำไมไม่ใช้รูปแบบของ EventHook stackoverflow.com/questions/1092531/event-system-in-python/…ตาม @Lasse answer? แทนที่จะเป็นสิ่งที่วนรอบ?
Andre Miras

1
คิวไม่ใช่ยานพาหนะที่ดีที่สุดในการสื่อสารข้อผิดพลาดกลับเว้นแต่คุณต้องการมีคิวเต็ม สิ่งก่อสร้างที่ดีกว่าคือการทำเกลียวกิจกรรม ()
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.futuresรวมอยู่ใน Python 3.2 และมีให้ในฐานะโมดูลbackportedfuturesสำหรับรุ่นก่อนหน้า


5
แม้ว่าสิ่งนี้จะไม่ทำสิ่งที่ OP ต้องการ แต่เป็นสิ่งที่ฉันต้องการ ขอบคุณ.
นักฟิสิกส์บ้า

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()วิธีการลงเป็นรุ่นที่ถูกจัดการ (ถ้าคุณกำลังทำงานในเวอร์ชัน 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
ฉันไม่แน่ใจว่าทำไมคำตอบนี้ไม่เป็นที่นิยมเช่นกัน มีคนอื่นที่นี่ที่ทำการเผยแพร่ง่ายๆเช่นกัน แต่ต้องขยายชั้นเรียนและเอาชนะ คนนี้ทำในสิ่งที่หลายคนคาดหวังและต้องการเปลี่ยนจากเฉพาะเธรดเป็น ProagatingThread และแท็บพื้นที่ 4 แท็บดังนั้นการคัดลอก / วางของฉันจึงเป็นเรื่องเล็กน้อย :-) ... การปรับปรุงเพียงอย่างเดียวที่ฉันแนะนำคือใช้ six.raise_from () เพื่อให้คุณได้ชุดการติดตามสแต็กที่ดีแทนที่จะเป็นเพียงสแต็กสำหรับ เว็บไซต์ของ reraise
aggieNick02

ขอบคุณมาก. ทางออกที่ง่ายมาก
sonulohani

ปัญหาของฉันคือฉันมีเธรดลูกหลายเธรด การรวมจะดำเนินการตามลำดับและอาจยกข้อยกเว้นจากเธรดที่เข้าร่วมในภายหลัง มีวิธีแก้ไขปัญหาของฉันอย่างง่ายไหม? รันการเข้าร่วมพร้อมกันไหม
ชวน

ขอบคุณที่ทำงานได้อย่างสมบูรณ์แบบ! ไม่แน่ใจว่าทำไมมันถึงไม่ถูกจัดการโดย python tho …
GG

นี่เป็นคำตอบที่เป็นประโยชน์มากที่สุดคำตอบนี้เป็นคำทั่วไปที่กว้างกว่าคำตอบอื่น ๆ ที่เรียบง่าย จะใช้มันในโครงการ!
Konstantin Sekeresh

30

แม้ว่าจะเป็นไปไม่ได้ที่จะตรวจจับข้อยกเว้นที่ถูกโยนในเธรดอื่นโดยตรง แต่นี่เป็นโค้ดที่ค่อนข้างชัดเจนที่จะได้รับบางสิ่งที่ใกล้เคียงกับฟังก์ชั่นนี้ เธรดย่อยของคุณต้องคลาสย่อยExThreadแทนthreading.Threadและเธรดหลักต้องเรียกchild_thread.join_with_exception()เมธอดแทนchild_thread.join()เมื่อรอเธรดเพื่อให้งานเสร็จ

รายละเอียดทางเทคนิคของการนำไปใช้นี้: เมื่อเธรดลูกส่งข้อยกเว้นจะถูกส่งไปยังพาเรนต์ผ่าน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 KeyboardInterruptจะถูกเพิกเฉยหากถูกยกในเธรดพื้นหลัง
ArtOfWarfare

join_with_exceptionแฮงค์ไปเรื่อย ๆ หากเรียกว่าครั้งที่สองบนเธรดที่ตายแล้ว คงที่: github.com/fraserharris/threading-extensions/blob/master/ …
Fraser Harris

ฉันไม่คิดว่า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:

รูปแบบอาร์กิวเมนต์ 3 สำหรับraiseหายไปใน Python 3 ดังนั้นเปลี่ยนบรรทัดสุดท้ายเป็น:

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

2
ทำไมคุณใช้ threading.Thread.join (ตัวเอง) แทน 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

ทางออกต่อไปนี้:

  • กลับไปที่เธรดหลักทันทีเมื่อมีข้อยกเว้นถูกเรียก
  • ไม่ต้องการคลาสที่ผู้ใช้กำหนดเพิ่มเติมเนื่องจากไม่ต้องการ:
    • ชัดเจน 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)แต่สิ่งนี้อาจหมายความว่าคุณจำเป็นต้องมี refactor

คลาสที่กำหนดเองพร้อมซีแมนทิกส์ข้อยกเว้นที่สมบูรณ์แบบ

ฉันสิ้นสุดการเขียนโค้ดอินเทอร์เฟซที่สมบูรณ์แบบสำหรับตัวเองที่: วิธีที่ถูกต้องในการ จำกัด จำนวนเธรดสูงสุดที่ทำงานพร้อมกันหรือไม่ ส่วน "ตัวอย่างคิวด้วยการจัดการข้อผิดพลาด" ชั้นเรียนนั้นมีวัตถุประสงค์เพื่อให้ทั้งความสะดวกสบายและให้คุณควบคุมการส่งและการจัดการข้อผิดพลาด

ทดสอบกับ Python 3.6.7, Ubuntu 18.04


4

นี่เป็นปัญหาเล็ก ๆ ที่น่ารังเกียจและฉันต้องการที่จะแก้ปัญหาของฉันวิธีการแก้ปัญหาอื่น ๆ ที่ฉันได้พบ (เช่น async.io) ดูมีแนวโน้ม แต่ยังแสดงกล่องดำสักหน่อย การเรียงลำดับของลูปคิว / เหตุการณ์เชื่อมโยงคุณกับการใช้งานบางอย่าง รหัสที่มาฟิวเจอร์สพร้อมกัน แต่เป็นรอบเพียง 1,000 เส้นและง่ายต่อการเข้าใจ มันทำให้ฉันสามารถแก้ปัญหาของฉันได้อย่างง่ายดาย: สร้างเธรดผู้ทำงานเฉพาะกิจโดยไม่ต้องตั้งค่ามากนักและสามารถจับข้อยกเว้นในเธรดหลักได้

โซลูชันของฉันใช้ 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

ในฐานะที่เป็น Noobie สำหรับ Threading ฉันใช้เวลานานในการทำความเข้าใจวิธีการใช้โค้ดของ 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

วิธีที่คล้ายกันเช่น RickardSjogren โดยไม่มี Queue, 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 เป็น 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()

แน่นอนคุณสามารถให้มันจัดการข้อยกเว้นบางวิธีอื่น ๆ จากการเข้าสู่ระบบเช่นการพิมพ์ออกมาหรือให้มันออกไปยังคอนโซล

สิ่งนี้อนุญาตให้คุณใช้คลาส 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'

ฉันไม่มีประสบการณ์เพียงพอในการทำงานกับเธรดเพื่ออ้างว่านี่เป็นวิธีที่ปลอดภัยอย่างสมบูรณ์ แต่มันได้ผลสำหรับฉันและฉันชอบความยืดหยุ่น


คุณยกเลิกการเชื่อมต่อหลังจากเข้าร่วม ()?
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จัดทำข้อมูลให้ตรงกันกลุ่ม WGGซึ่งโดยเฉพาะอย่างยิ่งการเผยแพร่ข้อยกเว้นจากเธรดผู้ทำงานกลับกลายไปยังหัวข้อหลัก ตัวอย่างเช่น:

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