Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/csharp/303.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# 如何捕获从延续引发的未处理异常?_C#_Winforms_Exception_.net 4.0_Task - Fatal编程技术网

C# 如何捕获从延续引发的未处理异常?

C# 如何捕获从延续引发的未处理异常?,c#,winforms,exception,.net-4.0,task,C#,Winforms,Exception,.net 4.0,Task,我无法捕获从延续任务引发的未处理异常 为了演示这个问题,让我向您展示一些有效的代码。此代码来自一个基本的Windows窗体应用程序 首先,program.cs: using System; using System.Windows.Forms; namespace WindowsFormsApplication3 { static class Program { [STAThread] static void Main() {

我无法捕获从延续任务引发的未处理异常

为了演示这个问题,让我向您展示一些有效的代码。此代码来自一个基本的Windows窗体应用程序

首先,program.cs

using System;
using System.Windows.Forms;

namespace WindowsFormsApplication3
{
    static class Program
    {
        [STAThread]
        static void Main()
        {
            Application.EnableVisualStyles();
            Application.SetCompatibleTextRenderingDefault(false);
            Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

            Application.ThreadException += (sender, args) =>
            {
                MessageBox.Show(args.Exception.Message, "ThreadException");
            };

            AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
            {
                MessageBox.Show(args.ExceptionObject.ToString(), "UnhandledException");
            };

            try
            {
                Application.Run(new Form1());
            }

            catch (Exception exception)
            {
                MessageBox.Show(exception.Message, "Application.Run() exception");
            }
        }
    }
}
这将订阅所有可用的异常处理程序。(实际上只引发了
Application.ThreadException
,但我想确保排除了所有其他可能性。)

下面是一个表单,该表单会正确显示未处理异常的消息:

using System;
using System.Threading;
using System.Windows.Forms;

namespace WindowsFormsApplication3
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        protected override void OnShown(EventArgs e)
        {
            base.OnShown(e);
            doWork();
            this.Close();
        }

        void doWork()
        {
            Thread.Sleep(1000); // Simulate work.
            throw new InvalidOperationException("TEST");
        }
    }
}
运行此操作时,一秒钟后将出现一个消息框,显示异常消息

如您所见,我正在编写一个“请等待”样式的表单,它完成一些后台工作,然后在工作完成后自动关闭

因此,我向
OnShown()
添加了一个后台任务,如下所示:

protected override void OnShown(EventArgs e)
{
    base.OnShown(e);

    Task.Factory.StartNew(doWork).ContinueWith
    (
        antecedent =>
        {
            if (antecedent.Exception != null)
                throw antecedent.Exception;

            this.Close();
        },
        TaskScheduler.FromCurrentSynchronizationContext()
    );
}
我认为continuation将在表单的上下文中运行,并且异常将以某种方式被我在
program.cs
中订阅的一个未处理的异常事件捕获

不幸的是,没有捕获任何内容,表单保持打开状态,没有任何错误迹象

有人知道我应该如何做到这一点吗?如果在辅助任务中抛出异常,并且没有显式捕获和处理,那么它将被外部未处理的异常事件捕获


[编辑]

Niyoko Yuliawan建议使用
wait
。不幸的是,我不能使用它,因为这个项目一直使用古老的.NET4.0。但是,我可以确认,如果我可以使用它,使用
wait
将解决它

为了完整起见,如果我使用.Net 4.5或更高版本,这里有一个更简单、更可读的解决方案:

protected override async void OnShown(EventArgs e)
{
    base.OnShown(e);
    await Task.Factory.StartNew(doWork);
    this.Close();
}
[编辑二]

雷登桑也提出了一个有用的答案,但不幸的是,这也不起作用。我认为这只是稍微改变了问题。下面的代码也无法导致显示异常消息-即使在调试器下运行它并在
antecedent=>{throw-antecedent.exception;}
行上设置断点,也表明正在到达该行

protected override void OnShown(EventArgs e)
{
    base.OnShown(e);

    var task = Task.Factory.StartNew(doWork);

    task.ContinueWith
    (
        antecedent => { this.Close(); },
        CancellationToken.None,
        TaskContinuationOptions.OnlyOnRanToCompletion, 
        TaskScheduler.FromCurrentSynchronizationContext()
    );

    task.ContinueWith
    (
        antecedent => { throw antecedent.Exception; },
        CancellationToken.None,
        TaskContinuationOptions.OnlyOnFaulted,
        TaskScheduler.FromCurrentSynchronizationContext()
    );
}

已检查任务计划程序。未观察到的任务异常事件。 有了它,您可以捕获在垃圾收集发生后工作线程中发生的未观察到的异常

    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        Application.SetUnhandledExceptionMode(UnhandledExceptionMode.CatchException);

        TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;

        Application.ThreadException += (sender, args) =>
        {
            MessageBox.Show(args.Exception.Message, "ThreadException");
        };

        AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
        {
            MessageBox.Show(args.ExceptionObject.ToString(), "UnhandledException");
        };

        try
        {
            Application.Run(new Form1());
        }

        catch (Exception exception)
        {
            MessageBox.Show(exception.Message, "Application.Run() exception");
        }
    }
    private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
    {
        throw e.Exception;
    }
以下是任务:

protected override void OnShown(EventArgs e)
{
    base.OnShown(e);
    Task.Factory.StartNew(doWork);
    Thread.Sleep(2000);
    GC.Collect();
    GC.WaitForPendingFinalizers();
}

void doWork()
{
    Thread.Sleep(1000); // Simulate work.
    throw new InvalidOperationException("TEST");
}

更新

您提到您正在使用.Net 4.0。不如使用
async/await
特性,它不会阻塞用户界面。您只需将
MicrosoftAsync
从nuget添加到您的项目中即可

现在修改OnShown如下(我为
doWork
添加了代码,因此它可以更加明显):

旧答案 找到解决方案,添加
task.Wait()。不确定它为什么有效:

        protected override void OnShown(EventArgs e)
        {
            base.OnShown(e);

            var task = Task.Factory.StartNew(doWork)
                .ContinueWith
                (
                    antecedent => { this.Close(); },
                    CancellationToken.None,
                    TaskContinuationOptions.OnlyOnRanToCompletion,
                    TaskScheduler.FromCurrentSynchronizationContext()
                )
                .ContinueWith
                (
                    antecedent => { 
                        throw antecedent.Exception;
                    },
                    CancellationToken.None,
                    TaskContinuationOptions.OnlyOnFaulted,
                    TaskScheduler.FromCurrentSynchronizationContext()
                );

            // this is where magic happens.
            task.Wait();
        }

我找到了一个解决方法,我将把它作为一个答案发布(但我至少一天内不会把它作为答案,以防有人同时想出更好的答案!)

我决定走老路,用
BeginInvoke()
代替延续。然后,它似乎按照预期工作:

protected override void OnShown(EventArgs e)
{
    base.OnShown(e);

    Task.Factory.StartNew(() =>
    {
        try
        {
            doWork();
        }

        catch (Exception exception)
        {
            this.BeginInvoke(new Action(() => { throw new InvalidOperationException("Exception in doWork()", exception); }));
        }

        finally
        {
            this.BeginInvoke(new Action(this.Close));
        }
    });
}

然而,至少我现在知道了为什么我的原始代码不起作用了。查看Evk的答案了解原因

即使您在UI线程上抛出异常,也不意味着它最终会进入
应用程序。线程异常
。在这种情况下,它被
Task
异常处理程序(设置
Task.exception
Task.IsFaulted
等的处理程序)截获,并实际成为未观察到的任务异常。可以将其视为
任务
使用
控件。调用
e以执行继续操作,而
控件。调用
是同步的,这意味着它的异常无法传递给
应用程序。线程异常
,因为winforms不知道它是否将由调用方处理(这与
Control.BeginInvoke
不同,它的异常将始终传递给
应用程序.ThreadException

您可以通过订阅
TaskScheduler.unobservedtaskeexception
来验证这一点,并在继续操作完成后的一段时间强制执行垃圾收集


长话短说-在这种情况下,您的延续是否在非的UI线程上运行无关紧要-您延续中所有未处理的异常都将以UnobservedTaskeException而不是ThreadException结束。在这种情况下,手动调用您的异常处理程序是合理的,而不是依赖那些“最后手段”处理程序。

我不知道抛出的异常是否为非CLS投诉,但如果是,则应仅使用catch{}捕获它.@mybirthname否,这是一个基本的
InvalidOperationException
,符合CLS。请注意,如果不是从后台任务中抛出,则可以正确捕获异常。@NiyokoYuliawan这可能有效,但不幸的是(根据问题上的
.net-4.0
标记)我不能使用它。是的,对不起,我没有看到你的标记。@NiyokoYuliawan这是个好主意,事实上,如果你能使用它,它确实可以工作(遗憾的是我不能!)-我将在问题中添加一些关于它的内容!不幸的是,这不起作用-我认为当我执行
if(antecedent.exception!=null)时,异常正在被“观察到”
因此事件不会被触发(尽管即使我注释了这一行,它似乎也不会触发…)你是对的..没有与continuewith进行检查..在我发现一些有用的东西后会更新我的答案,因为它会将其转换为同步调用-唉,我们不能使用它,因为它会在
OnShown()中阻止UI
-不阻塞UI是使用后台任务的关键!你是对的。这不是答案,但我会将其保留为个人历史记录。我明白你的意思,但我不能手动调用异常处理程序,因为表单位于类库中,并且
protected override void OnShown(EventArgs e)
{
    base.OnShown(e);

    Task.Factory.StartNew(() =>
    {
        try
        {
            doWork();
        }

        catch (Exception exception)
        {
            this.BeginInvoke(new Action(() => { throw new InvalidOperationException("Exception in doWork()", exception); }));
        }

        finally
        {
            this.BeginInvoke(new Action(this.Close));
        }
    });
}