线程中的异常处理

13

最近我参加了一次面试。面试官给了我一个代码片段。我知道,面试官是从albhari的线程示例中获取的。

public static void Main() 
{
    try 
    {
        new Thread (Go).Start();
    }
    catch (Exception ex)
    {
        // We'll never get here!
       Console.WriteLine ("Exception!");
    }
}

static void Go() { throw null; }

将上述代码修改为:

public static void Main()
{
    new Thread (Go).Start();
}

static void Go() 
{
    try 
    {
        ...
        throw null; // this exception will get caught below
        ...
    }
    catch (Exception ex) 
    {
        Typically log the exception, and/or signal another thread
        that we've come unstuck
        ...
    }
}

这个人可能是处理异常的好人选。

有人问我:“除了上述方法之外,还有哪些其他替代方案适合作为好的解决方案?”,但很难找到替代方案,所以我在这里提出来,以收集您的建议。

5个回答

20

在一个线程中抛出的异常通常无法在另一个线程中捕获。

最好在函数Go中捕获它并显式地将其传递到主线程。

然而,如果您只想记录所有线程中未处理的消息,您可以使用AppDomain.UnhandledException事件或等效的应用程序类事件(如果您正在开发WinForms或WPF应用程序)。


13
请注意,在AppDomain.UnhandledException中无法处理异常,您会收到通知,但应用程序仍将关闭。 - Lucero
2
可以通过设置v1.x兼容模式来防止应用程序停止。为此,在<runtime>部分的app.config中必须添加<legacyUnhandledExceptionPolicy enabled="1"/>元素。 - elder_george

4

还有哪些替代方案可以作为好的解决方案?

解决什么问题?你试图解决什么问题?

如果使用 BackgroundWorker 而不是 Thread,它具有 RunWorkerCompleted 事件,在其中您可以检查 RunWorkerCompletedEventArgs 参数的 Error 属性。这通常用于 WinForms 或 WPF 应用程序,因为 Visual Studio 设计器对 BackgroundWorker 有很好的支持。

您还可以为 Go() 定义一个委托,并在其上调用 BeginInvoke()。当然,您也需要 EndInvoke()。

此外,随意启动随机线程通常不是一个好主意。ThreadPool.QueueUserWorkItem、BackgroundWorker 或异步委托都使用 ThreadPool,并且是建议的。


我想表达的是:原始代码已经失效了。你的第一个选择是一种替代方案。如果它能够满足要求,那么你就不需要其他的选择了。你试图解决什么问题?“原始代码的替代方案”可以是任何东西。你可以提交解决数独难题的代码 - 那也是一种替代方案。但这是否合适呢?没有明确的需求,谁能说得准呢? - Cheeso
不,是面试官问了那个问题。:) 所以我没有说任何话。而且我也不知道如何处理这个问题。:) 谢谢你的建议。 - user184805
2
@Cheeso,请正确阅读问题,没有人需要任何建议,他在面试中遇到了这个问题,聪明的人告诉他找到一个替代方案,我相信请求者对您提出的所有建议都非常了解。 - Akash Kava

2

Joe Albahari的网站上列出了一些替代方案: http://www.albahari.com/threading/#_Exception_Handling

“然而,有一些情况下你不需要在工作线程上处理异常,因为.NET Framework会为你处理。这些将在接下来的章节中介绍,包括:
-异步委托
-BackgroundWorker
-任务并行库(条件适用)”


1
我认为最简单的方法是:

我认为这是最简单的方法:

BackgroundWorker bw = new BackgroundWorker();
bw.DoWork += new DoWorkEventHandler((object sender2, DoWorkEventArgs e2) =>
{
    throw new Exception("something bad");
    e2.Result = 1 + 1;
});
bw.RunWorkerCompleted += new RunWorkerCompletedEventHandler((object sender2, RunWorkerCompletedEventArgs e2) =>
{
    if (e2.Error != null)
    {
        Console.WriteLine("Error: " + e2.Error.Message);
    }
});
bw.RunWorkerAsync();

但是如果您想同步线程(也许这是在GUI线程之外的线程),有另一种方法可能更适合一些人:

    private class FileCopier
    {
        public bool failed = false;
        public Exception ex = null;
        public string localPath;
        public string dstPath;
        public FileCopier(string localPath, string dstPath)
        {
            this.localPath = localPath;
            this.dstPath = dstPath;
        }

        public void Copy()
        {
            try{
                throw new Exception("bad path");
            }catch(Exception ex2)
            {
                ex = ex2;
                failed = true;
            }
        }
    }

    public static void Main()
    {
        FileCopier fc = new FileCopier("some path", "some path");
        Thread t = new Thread(fc.Copy);
        t.Start();
        t.Join();
        if (fc.failed)
            Console.WriteLine(fc.ex.Message);
    }

请注意,如果您有多个线程并循环遍历并加入所有线程,则第二个示例将更有意义...但我保持了简单的示例。
第三种模式将使用Task Factory,这更加整洁:
private static test(){
    List<Task<float>> tasks = new List<Task<float>>();
    for (float i = -3.0f; i <= 3.0f; i+=1.0f)
    {
        float num = i;
        Console.WriteLine("sent " + i);
        Task<float> task = Task.Factory.StartNew<float>(() => Div(5.0f, num));
        tasks.Add(task);
    }

    foreach(Task<float> t in tasks)
    {
        try
        {
            t.Wait();
            if (t.IsFaulted)
            {
                Console.WriteLine("Something went wrong: " + t.Exception.Message);
            }
            else
            {
                Console.WriteLine("result: " + t.Result);
            }
        }catch(Exception ex)
        {
            Console.WriteLine("Error: " + ex.Message);
        }

    }
}

private static float Div(float a, float b)
{
    Console.WriteLine("got " + b);
    if (b == 0) throw new Exception("Divide by zero");
    return a / b;
}

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接