【问题标题】:Inner threads exception handling内线程异常处理
【发布时间】:2013-10-06 04:20:00
【问题描述】:

我有一个对象 A,我想用对象的 B 方法从中执行新线程。我可以使用 Task.CreateNew 等。问题是我不知道如何处理新线程中的异常。

通常我想要的是带有对象 B 方法的内部线程抛出异常,父对象 A 将捕获并关闭它的执行,以及对象 B。

  • 我根本无法向主循环添加代码

  • 在主循环完成后捕获异常是不可接受的,我想要 及时捕获内部线程异常

有什么方法可以实现吗?

在下面的代码中我没有异常处理并且主线程继续:

static void Main()
{
    Console.WriteLine("start");
    Task.Factory.StartNew(PrintTime, CancellationToken.None);

    for (int i = 0; i < 20; i++)
    {
        Console.WriteLine("master thread i={0}", i + 1);
        Thread.Sleep(1000);
    }
}

private static void PrintTime()
{
    for (int i = 0; i < 10; i++)
    {
        Console.WriteLine("inner thread i={0}",i+1);
        Thread.Sleep(1000);
    }

    throw new Exception("exception");
}

【问题讨论】:

    标签: c# .net multithreading


    【解决方案1】:

    保留对您的任务实例的引用,并在您准备好处理其结果时对其调用Wait。在任务执行期间引发的任何未处理的内部异常都将包装在 AggregateException 中,而这又会从 Wait 方法中引发。

    static void Main()
    {
        Console.WriteLine("start");
        Task task = Task.Factory.StartNew(PrintTime, CancellationToken.None);
    
        for (int i = 0; i < 20; i++)
        {
            Console.WriteLine("master thread i={0}", i + 1);
            Thread.Sleep(1000);
    
            // Stop iterating in case of unhandled exception in inner task.
            if (task.Status == TaskStatus.Faulted)
                break;
        }
    
        try
        {
            task.Wait();
        }
        catch (AggregateException ae) 
        {
            ae.Handle((x) =>
            {
                 Console.WriteLine("Exception: " + x.ToString());
            });
        }
    }
    

    【讨论】:

    • 这意味着主线程必须完成才能检查异常,而我想终止内部线程异常上的任何线程
    • @eugeneK:我已经编辑了答案以在内部异常的情况下停止迭代。这可以进一步改进,甚至在 1000 毫秒延迟结束之前停止主线程。
    • 我无法更改主循环,
    • 因为主循环是由数千个动态方法组成的组。我确实简化了示例的代码。
    【解决方案2】:

    无论哪个线程首先抛出异常,以下解决方案都有效:

    static void Main()
    {
        Console.WriteLine("start");
    
        var innerCts = new CancellationTokenSource();
        Exception mainException = null;
        var mainThread = new Thread(() => SafeMainThread(innerCts, ref mainException));
        mainThread.Start();
    
        var innerTask = Task.Factory.StartNew(state => PrintTime(state),
                                              innerCts,
                                              innerCts.Token,
                                              TaskCreationOptions.LongRunning,
                                              TaskScheduler.Default);
    
        var innerFault = innerTask.ContinueWith(t => { Console.WriteLine("Inner thread caused " + t.Exception.InnerException.GetType().Name + ". Main thread is being aborted..."); mainThread.Abort(); },
                                                TaskContinuationOptions.OnlyOnFaulted);
    
        var innerCancelled = innerTask.ContinueWith(_ => Console.WriteLine("Inner thread cancelled."),
                                                    TaskContinuationOptions.OnlyOnCanceled);
    
        var innerSucceed = innerTask.ContinueWith(_ => Console.WriteLine("Inner thread completed."),
                                                  TaskContinuationOptions.OnlyOnRanToCompletion);
    
        try
        {
            innerTask.Wait();
        }
        catch (AggregateException)
        {
            // Ignore.
        }
    
        mainThread.Join();
    
        Console.ReadLine();
    }
    
    private static void SafeMainThread(CancellationTokenSource innerCts, ref Exception mainException)
    {
        try
        {
            MainThread();
            Console.WriteLine("Main thread completed.");
        }
        catch (ThreadAbortException)
        {
            Console.WriteLine("Main thread aborted.");
        }
        catch (Exception exception)
        {
            Console.WriteLine("Main thread caused " + exception.GetType().Name + ". Inner task is being canceled...");
    
            innerCts.Cancel();
            mainException = exception;
        }
    }
    
    private static void MainThread()
    {
        for (int i = 0; i < 20; i++)
        {
            Console.WriteLine("master thread i={0}", i + 1);
            Thread.Sleep(500);
        }
        throw new Exception("exception");
    }
    
    private static void PrintTime(object state)
    {
        var cts = (CancellationTokenSource)state;
    
        for (int i = 0; i < 10; i++)
        {
            cts.Token.ThrowIfCancellationRequested();
    
            Console.WriteLine("inner thread i={0}", i + 1);
            Thread.Sleep(500);
        }
    
        throw new Exception("exception");
    }
    

    【讨论】:

      【解决方案3】:

      尝试拦截内部异常并在主循环中检查其值,就好像取消请求一样:

      static void Main()
      {
          Console.WriteLine("start");
      
          try
          {
              AggregateException innerException = null;
      
              Task.Factory.StartNew(PrintTime, CancellationToken.None)
                          .ContinueWith(t => innerException = t.Exception, TaskContinuationOptions.OnlyOnFaulted);
      
              for (int i = 0; i < 20; i++)
              {
                  if (innerException != null)
                      throw innerException;
      
                  Console.WriteLine("master thread i={0}", i + 1);
                  Thread.Sleep(1000);
              }
          }
          catch (AggregateException)
          {
              Console.WriteLine("Inner thread caused exception. Main thread handles that exception.");
          }
      }
      

      【讨论】:

        【解决方案4】:

        您可以尝试在发生异常时运行异常处理程序。你可以使用一个标志,我在示例中使用了异常来检查异常发生本身:

            private static AggregateException exception = null;
        
            static void Main(string[] args)
            {
                Console.WriteLine("Start");
        
                Task.Factory.StartNew(PrintTime, CancellationToken.None).ContinueWith(HandleException, TaskContinuationOptions.OnlyOnFaulted);
        
                for (int i = 0; i < 20; i++)
                {
                    Console.WriteLine("Master Thread i={0}", i + 1);
                    Thread.Sleep(1000);
                    if (exception != null)
                    {
                        break;
                    }
                }
        
                Console.WriteLine("Finish");
                Console.ReadLine();
            }
        
            private static void HandleException(Task task)
            {
                exception = task.Exception;
                Console.WriteLine(exception);
            }
        
            private static void PrintTime()
            {
                for (int i = 0; i < 10; i++)
                {
                    Console.WriteLine("Inner Thread i={0}", i + 1);
                    Thread.Sleep(1000);
                }
        
                throw new Exception("exception");
            }
        

        【讨论】:

        • 在主循环中做不到
        【解决方案5】:

        如果不能修改主体内部,我不知道如何对其进行细粒度控制。我目前看到的解决方案是将主体包装到托管线程中,如果内部线程抛出异常,则允许中止它:

        static void Main()
        {
            Console.WriteLine("start");
        
            var mainThread = new Thread(MainThread);
            mainThread.Start();
        
            var task = Task.Factory
                           .StartNew(PrintTime)
                           .ContinueWith(t => { Console.WriteLine("Inner thread caused exception. Main thread is being aborted."); mainThread.Abort(); },
                                         TaskContinuationOptions.OnlyOnFaulted);
            task.Wait();
        
            Console.WriteLine("Waiting for main thread to abort...");
            mainThread.Join();
            Console.WriteLine("Main thread aborted.");
        
            Console.ReadLine();
        }
        
        private static void MainThread()
        {
            for (int i = 0; i < 20; i++)
            {
                Console.WriteLine("master thread i={0}", i + 1);
                Thread.Sleep(1000);
            }
        }
        
        private static void PrintTime()
        {
            for (int i = 0; i < 10; i++)
            {
                Console.WriteLine("inner thread i={0}", i + 1);
                Thread.Sleep(1000);
            }
        
            throw new Exception("exception");
        }
        

        【讨论】:

        • 如果主线程因异常而崩溃,我的代码不会有未处理的异常吗?
        • 你没有写,答案应该考虑到主线程的异常。但是好的,我可以解决这个问题,并且摆脱主线程比内线程持续时间更长的假设,并且还考虑到两个线程都将完成其任务而不抛出异常的情况。
        • 对不起,我会尽快检查
        • 试过了,MainThread 异常未处理,尽管我已经用 try/catch 包装了主要内容
        猜你喜欢
        • 1970-01-01
        • 2014-04-11
        • 2013-09-10
        • 2014-03-23
        • 1970-01-01
        • 2011-12-31
        • 1970-01-01
        • 2011-04-07
        • 2016-04-24
        相关资源
        最近更新 更多