จับข้อยกเว้นที่ถูกโยนในเธรดที่แตกต่างกัน


110

หนึ่งในวิธีของฉัน ( Method1) สร้างเธรดใหม่ เธรดนั้นดำเนินการเมธอด ( Method2) และในระหว่างการดำเนินการจะมีการยกเว้น ฉันต้องการรับข้อมูลข้อยกเว้นนั้นเกี่ยวกับวิธีการโทร ( Method1)

มีบางครั้งที่ฉันสามารถจับข้อยกเว้นMethod1นี้ได้Method2หรือไม่?

คำตอบ:


182

ใน. NET 4ขึ้นไปคุณสามารถใช้Task<T>คลาสแทนการสร้างเธรดใหม่ได้ จากนั้นคุณจะได้รับข้อยกเว้นโดยใช้.Exceptionsคุณสมบัติบนวัตถุงานของคุณ มี 2 ​​วิธีในการทำ:

  1. ในวิธีการแยก: // คุณประมวลผลข้อยกเว้นในเธรดของงานบางอย่าง

    class Program
    {
        static void Main(string[] args)
        {
            Task<int> task = new Task<int>(Test);
            task.ContinueWith(ExceptionHandler, TaskContinuationOptions.OnlyOnFaulted);
            task.Start();
            Console.ReadLine();
        }
    
        static int Test()
        {
            throw new Exception();
        }
    
        static void ExceptionHandler(Task<int> task)
        {
            var exception = task.Exception;
            Console.WriteLine(exception);
        }
    }
  2. ในวิธีการเดียวกัน: // คุณประมวลผลข้อยกเว้นในเธรดของผู้โทร

    class Program
    {
        static void Main(string[] args)
        {
            Task<int> task = new Task<int>(Test);
            task.Start();
    
            try
            {
                task.Wait();
            }
            catch (AggregateException ex)
            {
                Console.WriteLine(ex);    
            }
    
            Console.ReadLine();
        }
    
        static int Test()
        {
            throw new Exception();
        }
    }

AggregateExceptionโปรดทราบว่าข้อยกเว้นที่คุณได้รับคือ ข้อยกเว้นที่แท้จริงทั้งหมดสามารถใช้ได้ผ่านทางex.InnerExceptionsทรัพย์สิน

ใน. NET 3.5คุณสามารถใช้รหัสต่อไปนี้:

  1. // คุณประมวลผลข้อยกเว้นในเธรดของเด็ก

    class Program
    {
        static void Main(string[] args)
        {
            Exception exception = null;
            Thread thread = new Thread(() => SafeExecute(() => Test(0, 0), Handler));
            thread.Start();            
    
            Console.ReadLine();
        }
    
        private static void Handler(Exception exception)
        {        
            Console.WriteLine(exception);
        }
    
        private static void SafeExecute(Action test, Action<Exception> handler)
        {
            try
            {
                test.Invoke();
            }
            catch (Exception ex)
            {
                Handler(ex);
            }
        }
    
        static void Test(int a, int b)
        {
            throw new Exception();
        }
    }
  2. หรือ // คุณประมวลผลข้อยกเว้นในเธรดของผู้โทร

    class Program
    {
        static void Main(string[] args)
        {
            Exception exception = null;
            Thread thread = new Thread(() => SafeExecute(() => Test(0, 0), out exception));
    
            thread.Start();            
    
            thread.Join();
    
            Console.WriteLine(exception);    
    
            Console.ReadLine();
        }
    
        private static void SafeExecute(Action test, out Exception exception)
        {
            exception = null;
    
            try
            {
                test.Invoke();
            }
            catch (Exception ex)
            {
                exception = ex;
            }
        }
    
        static void Test(int a, int b)
        {
            throw new Exception();
        }
    }

ขออภัยฉันลืมบอกว่าฉันใช้. NET 3.5 ตามความเข้าใจของฉันงานคือ 4.0 อะไร?
Silverlight Student

2
@SilverlightStudent โอเคฉันเพิ่งอัปเดตคำตอบเพื่อตอบสนองความต้องการของคุณ
oxilumin

@oxilumin: ขอบคุณและชื่นชมมาก อีกหนึ่งคำถามติดตาม หากเมธอด Test () ของคุณใช้อาร์กิวเมนต์น้อยเกินไปคุณจะแก้ไขเมธอด SafeExecute สำหรับอาร์กิวเมนต์เหล่านั้นได้อย่างไร
Silverlight Student

2
@SilverlightStudent Testในกรณีนี้ผมจะผ่านแลมบ์ดาแทน ถูกใจ() => Test(myParameter1, myParameter2)
oxilumin

2
@SilverlightStudent: อัพเดท
oxilumin

9

คุณไม่สามารถจับข้อยกเว้นในวิธีที่ 1 อย่างไรก็ตามคุณสามารถจับข้อยกเว้นใน Method2 และบันทึกลงในตัวแปรที่เธรดเดิมของการดำเนินการสามารถอ่านและใช้งานได้


ขอบคุณสำหรับคำตอบของคุณ ดังนั้นถ้า Method1 เป็นส่วนหนึ่งของ Class1 และฉันมีตัวแปรประเภท Exception ในคลาสนั้น เมื่อใดก็ตามที่ Method2 แสดงข้อยกเว้นจะตั้งค่าตัวแปรข้อยกเว้นใน Class1 ด้วย ฟังดูเป็นการออกแบบที่ยุติธรรมหรือไม่? มีแนวทางปฏิบัติที่ดีที่สุดในการจัดการสถานการณ์นี้หรือไม่?
Silverlight Student

ถูกต้องคุณแค่จัดเก็บข้อยกเว้นและเข้าถึงในภายหลัง ไม่ใช่เรื่องแปลกที่เมธอดจะทำงานในอนาคต (โดยเฉพาะการเรียกกลับเมื่อ Method2 เสร็จสมบูรณ์) เพื่อลบข้อยกเว้นนั้นใหม่ราวกับว่าพวกเขาได้ก่อให้เกิดขึ้น แต่สิ่งนี้ขึ้นอยู่กับสิ่งที่คุณต้องการจริงๆ
ermau

0

วิธีที่ง่ายที่สุดในการแบ่งปันข้อมูลระหว่างเธรดต่างๆมีshared dataดังนี้ (บางส่วนเป็นรหัสหลอก):

class MyThread
{
   public string SharedData;

   public void Worker()
   {
      ...lengthy action, infinite loop, etc...
      SharedData = "whatever";
      ...lengthy action...
      return;
   }
}

class Program
{
   static void Main()
   {
      MyThread m = new MyThread();
      Thread WorkerThread = new Thread(m.Worker);
      WorkerThread.Start();

      loop//or e.g. a Timer thread
      {
         f(m.SharedData);
      }
      return;
   }
}

คุณสามารถอ่านเกี่ยวกับวิธีการนี้ได้ในคำแนะนำที่ดีเกี่ยวกับมัลติเธรดอย่างไรก็ตามฉันต้องการอ่านเกี่ยวกับเรื่องนี้O'Reilly book C# 3.0 in a nutshellโดยพี่น้อง Albahari (2007) ซึ่งสามารถเข้าถึงได้อย่างอิสระบน Google หนังสือเช่นเดียวกับหนังสือรุ่นใหม่กว่า เพราะมันยังครอบคลุมการรวมเธรดเบื้องหน้าและเธรดพื้นหลัง ฯลฯ ด้วยโค้ดตัวอย่างที่ดีและเรียบง่าย (ข้อจำกัดความรับผิดชอบ: ฉันเป็นเจ้าของสำเนาหนังสือเล่มนี้ที่ชำรุด)

ในกรณีที่คุณกำลังสร้างแอปพลิเคชัน WinForms การใช้ข้อมูลที่แชร์จะมีประโยชน์อย่างยิ่งเนื่องจากตัวควบคุม WinForm ไม่ปลอดภัยต่อเธรด การใช้การโทรกลับเพื่อส่งข้อมูลจากเธรดของผู้ปฏิบัติงานกลับไปยังการควบคุม WinForm เธรด UI หลักต้องใช้รหัสที่น่าเกลียดInvoke()เพื่อให้เธรดการควบคุมปลอดภัย การใช้ข้อมูลร่วมกันแทนและเธรดเดียวSystem.Windows.Forms.Timerกับระยะสั้นIntervalของการพูด 0.2 Invokeวินาทีคุณสามารถส่งข้อมูลจากด้ายคนที่จะควบคุมได้โดยไม่ต้อง


0

ฉันมีปัญหาโดยเฉพาะในการที่ฉันต้องการใช้รายการที่มีการควบคุมจากชุดทดสอบการรวมดังนั้นต้องสร้างเธรด STA รหัสที่ฉันลงท้ายด้วยมีดังนี้ใส่ไว้ที่นี่ในกรณีที่คนอื่นมีปัญหาเดียวกัน

    public Boolean? Dance(String name) {

        // Already on an STA thread, so just go for it
        if (Thread.CurrentThread.GetApartmentState() == ApartmentState.STA) return DanceSTA(name);

        // Local variable to hold the caught exception until the caller can rethrow
        Exception lException = null;

        Boolean? lResult = null;

        // A gate to hold the calling thread until the called thread is done
        var lGate = new ManualResetEvent(false);

        var lThreadStart = new ThreadStart(() => {
            try {
                lResult = DanceSTA(name);
            } catch (Exception ex) {
                lException = ex;
            }
            lGate.Set();
        });

        var lThread = new Thread(lThreadStart);
        lThread.SetApartmentState(ApartmentState.STA);
        lThread.Start();

        lGate.WaitOne();

        if (lException != null) throw lException;

        return lResult;
    }

    public Boolean? DanceSTA(String name) { ... }

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

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.