1

我有一个对象 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");
}
4

5 回答 5

2

保留对您的任务实例的引用,并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());
        });
    }
}
于 2013-09-30T10:36:00.043 回答
1

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

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");
}
于 2013-09-30T14:01:51.483 回答
0

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

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.");
    }
}
于 2013-09-30T10:50:09.583 回答
0

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

    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");
    }
于 2013-09-30T11:10:53.043 回答
0

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

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");
}
于 2013-09-30T12:10:38.933 回答