C# 如何捕获从延续引发的未处理异常?
我无法捕获从延续任务引发的未处理异常 为了演示这个问题,让我向您展示一些有效的代码。此代码来自一个基本的Windows窗体应用程序 首先,program.cs: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() {
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));
}
});
}