我无法捕获从延续任务中抛出的未处理异常。
为了演示这个问题,让我向您展示一些有效的代码。此代码来自基本的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()
);
}
我认为延续将在表单的上下文中运行,异常会以某种方式被我在program.cs
中订阅的未处理的异常事件捕获。
不幸的是没有抓住任何东西,表格保持打开状态,没有迹象表明出现了任何问题。
有没有人知道我应该怎么做,这样如果在worker任务中抛出异常并且没有明确地捕获和处理它,它将被外部未处理的异常事件捕获?
[编辑]
Niyoko Yuliawan建议使用await
。不幸的是我无法使用它,因为这个项目使用古老的.Net 4.0。但是,我可以确认使用await
如果可以使用它将解决它!
为了完整性,如果我使用的是.Net 4.5或更高版本,我可以使用更简单,更易读的解决方案:
protected override async void OnShown(EventArgs e)
{
base.OnShown(e);
await Task.Factory.StartNew(doWork);
this.Close();
}
[EDIT2]
raidensan也提出了一个有用的答案,但不幸的是,这也没有用。我认为它只会略微解决问题。以下代码也无法显示异常消息 - 即使在调试器下运行它并在行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()
);
}
答案 0 :(得分:2)
您检查了TaskScheduler.UnobservedTaskException
事件here。
有了这个,您可以捕获垃圾收集发生后工作线程中发生的未观察到的异常。
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");
}
答案 1 :(得分:2)
即使你在UI线程上抛出异常,它不意味着它最终会到达Application.ThreadException
。在这种情况下,它被Task
异常处理程序(设置Task.Exception
,Task.IsFaulted
等的处理程序)拦截,实际上变为未观察到的任务异常。可以认为Task
使用Control.Invok
e来执行延续,Control.Invoke
是同步的,这意味着它的异常无法传递给Application.ThreadException
,因为winforms不知道如果它将由调用者处理(与Control.BeginInvoke
不同,则异常将始终传递给Application.ThreadException
)。
您可以通过在续订完成后订阅TaskScheduler.UnobservedTaskException
和强制垃圾回收来验证这一点。
这么长的故事简短 - 在这种情况下,你的延续是否在UI线程上运行是无关紧要的 - 你的延续中所有未处理的异常将以UnobservedTaskException结束,而不是ThreadException。在这种情况下手动调用异常处理程序是合理的,而不是依赖那些“最后的”处理程序。
答案 2 :(得分:1)
<强>更新强>
您提到过您使用的是.Net 4.0。如何使用async/await
功能,它不会阻止UI。您只需要从nuget为项目添加Microsoft Async
。
现在修改OnShown如下(我添加了doWork
的代码,所以它可以更明显):
protected async override void OnShown(EventArgs e)
{
base.OnShown(e);
await Task.Factory.StartNew(() =>
{
Thread.Sleep(1000); // Simulate work.
throw new InvalidOperationException("TEST");
})
.ContinueWith
(
antecedent => { this.Close(); },
CancellationToken.None,
TaskContinuationOptions.OnlyOnRanToCompletion,
TaskScheduler.FromCurrentSynchronizationContext()
)
.ContinueWith
(
antecedent => {
throw antecedent.Exception;
},
CancellationToken.None,
TaskContinuationOptions.OnlyOnFaulted,
TaskScheduler.FromCurrentSynchronizationContext()
);
}
旧答案
找到解决方案,添加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();
}
答案 3 :(得分:1)
我找到了一个解决方法,我将作为答案发布(但我不会将此标记为至少一天的答案,以防有人在此期间提出更好的答案!)
我决定去老派并使用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的答案!