以下情形在 .NET 4.5 下运行,因此任何UnobservedTaskException
都不terminate the process。
我习惯于在应用程序的开头执行此操作,以监听任何抛出的UnobservedTaskException
:
private void WatchForUnobservedTaskExceptions()
{
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
args.Exception.Dump("Ooops");
};
}
当我想显式忽略任务抛出的任何异常时,我还有一个辅助方法:
public static Task IgnoreExceptions(Task task)
=> task.ContinueWith(t =>
{
var ignored = t.Exception.Dump("Checked");
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
因此,如果我有以下代码,请执行:
void Main()
{
WatchForUnobservedTaskExceptions();
var task = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
throw new InvalidOperationException();
});
IgnoreExceptions(task);
GC.Collect(2);
GC.WaitForPendingFinalizers();
Console.ReadLine();
}
从Console.ReadLine()
返回后,我们将看不到任何UnobservedTaskException
被抛出的情况。
但是,如果我将上述task
更改为开始使用async/await
,则其他一切与以前相同:
var task = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
throw new InvalidOperationException();
});
现在,我们将抛出UnobservedTaskException
。调试代码可以发现t.Exception
为null
时继续执行。
在两种情况下如何正确忽略异常?
答案 0 :(得分:4)
可以使用
var task = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
throw new InvalidOperationException();
}).Unwrap();
或
var task = Task.Run(async () =>
{
await Task.Delay(1000);
throw new InvalidOperationException();
});
请参见this blogpost about Task.Run vs Task.Factory.StartNew,了解如何将Task.Factory.StartNew与异步修饰符一起使用
通过在此处使用async关键字,编译器将映射此委托为
Func<Task<int>>
:调用委托将返回Task<int>
,以表示此调用的最终完成。并且由于委托是Func<Task<int>>
,所以TResult
是Task<int>
,因此't'的类型将是Task<Task<int>>
,而不是Task<int>
。>为处理此类情况,在.NET 4中,我们引入了Unwrap方法。
为什么不使用Task.Factory.StartNew?
..不了解异步委托。 ……。问题在于,当您将异步委托传递给StartNew时,自然会假定返回的任务代表该委托。但是,由于StartNew无法理解异步委托,因此该任务实际代表的只是该委托的开始。这是编码人员在异步代码中使用StartNew时遇到的第一个陷阱。
编辑
task
=>中var task = Task.Factory.StartNew(async (...))
的类型实际上是Task<Task<int>>
。您必须Unwrap
才能获得源任务。考虑到这一点:
您只能在Unwrap
上调用Task<Task>>
,因此可以在IgnoreExceptions
上添加一个重载来容纳它:
void Main()
{
WatchForUnobservedTaskExceptions();
var task = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
throw new InvalidOperationException();
});
IgnoreExceptions(task);
GC.Collect(2);
GC.WaitForPendingFinalizers();
Console.ReadLine();
}
private void WatchForUnobservedTaskExceptions()
{
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
args.Exception.Dump("Ooops");
};
}
public static Task IgnoreExceptions(Task task)
=> task.ContinueWith(t =>
{
var ignored = t.Exception.Dump("Checked");
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
public static Task IgnoreExceptions(Task<Task> task)
=> task.Unwrap().ContinueWith(t =>
{
var ignored = t.Exception.Dump("Checked");
},
CancellationToken.None,
TaskContinuationOptions.ExecuteSynchronously,
TaskScheduler.Default);
答案 1 :(得分:2)
var
和Task
的结合以及Task<T>
的关联关系掩盖了问题。如果我稍微重写一下代码,问题出在哪里就会变得很明显。
Task<int> task1 = Task.Factory.StartNew(() =>
{
Thread.Sleep(1000);
throw new InvalidOperationException();
return 1;
});
Task<Task<int>> task2 = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
throw new InvalidOperationException();
return 1;
});
这更好地说明了Peter Bons在说什么。