C# 为什么在使用async await时,不管观察结果如何,都会抛出UnobservedTaskException?
以下场景在.NET 4.5下运行,因此任何C# 为什么在使用async await时,不管观察结果如何,都会抛出UnobservedTaskException?,c#,.net,task,task-parallel-library,C#,.net,Task,Task Parallel Library,以下场景在.NET 4.5下运行,因此任何未观察到的taskeexception都不会出现 我有一个习惯,就是在我的应用程序开始时执行此命令,从而监听任何引发的未观察到的taskeexception: private void WatchForUnobservedTaskExceptions() { TaskScheduler.UnobservedTaskException += (sender, args) => { args.Exception.Dump("Ooop
未观察到的taskeexception
都不会出现
我有一个习惯,就是在我的应用程序开始时执行此命令,从而监听任何引发的未观察到的taskeexception
:
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();
}
在我们从控制台返回.ReadLine()
后,我们将不会看到抛出任何未观察到的taskeException
,这是我们所期望的
但是,如果我将上述任务
更改为开始使用async/await
,其他所有操作与之前相同:
var task = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
throw new InvalidOperationException();
});
现在我们得到抛出的unobservedtaskeexception
。调试代码显示继续执行时t.Exception
为null
如何在这两种情况下正确忽略异常?使用
var task = Task.Factory.StartNew(async () =>
{
await Task.Delay(1000);
throw new InvalidOperationException();
}).Unwrap();
或
请参阅关于将Task.Factory.StartNew与异步修饰符一起使用
通过在此处使用async关键字,编译器将此委托映射为Func
:调用委托将返回任务
,以表示此调用的最终完成。由于委托是Func
,TResult
是Task
,因此“t”的类型将是Task
,而不是Task
为了处理这类情况,在.NET4中,我们引入了展开方法
更多
为什么不使用Task.Factory.StartNew?
。。不理解异步委托。问题是,当您将异步委托传递给StartNew时,很自然地会假定返回的任务代表该委托。但是,由于StartNew不理解异步委托,因此该任务实际表示的只是该委托的开始。这是编码器在异步代码中使用StartNew时遇到的第一个陷阱之一
编辑
var task=task.Factory.StartNew(异步(…)
=>中的task
类型实际上是task
。您必须展开它才能获得源任务。考虑到这一点:
您只能在任务>
上调用展开
,因此可以向忽略异常
添加重载,以适应以下情况:
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);
void Main()
{
监视未观察到的异常();
var task=task.Factory.StartNew(异步()=>
{
等待任务。延迟(1000);
抛出新的InvalidOperationException();
});
忽略异常(任务);
GC.Collect(2);
GC.WaitForPendingFinalizers();
Console.ReadLine();
}
私有void watchforunobservedtaskeexceptions()
{
TaskScheduler.UnobservedTaskException+=(发送方,参数)=>
{
参数异常转储(“Ooops”);
};
}
公共静态任务忽略异常(任务任务)
=>task.ContinueWith(t=>
{
忽略var=t.Exception.Dump(“选中”);
},
取消令牌。无,
TaskContinuationOptions.Executes同步执行,
TaskScheduler.Default);
公共静态任务忽略异常(任务任务)
=>task.Unwrap().ContinueWith(t=>
{
忽略var=t.Exception.Dump(“选中”);
},
取消令牌。无,
TaskContinuationOptions.Executes同步执行,
TaskScheduler.Default);
将var
和Task
以及Task
的相互关系结合起来,掩盖了这个问题。如果我稍微重写一下代码,问题就显而易见了
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;
});
Task task1=Task.Factory.StartNew(()=>
{
睡眠(1000);
抛出新的InvalidOperationException();
返回1;
});
tasktask2=Task.Factory.StartNew(异步()=>
{
等待任务。延迟(1000);
抛出新的InvalidOperationException();
返回1;
});
这更好地说明了Peter Bons所说的。为什么不捕获异步函数中的异常呢。这样就不会出现未观察到的任务异常。helper方法的要点是避免在任何地方都进行捕获。这种情况到底有多现实?你不应该在Task.Run
或Task.Factory.StartNew
@PeterBons中包装一个已经异步的方法哦,公平地说,这是有用途的;不一定有用,但是sometimes@MarcGravellDump()
是任务的一种方法。Run()
不是选项,因为IgnoreExceptions
位于库中,不知道任务的来源。我似乎无法在IgnoreException
中使用Unwrap
,它无法编译。谢谢,这有助于更好地说明问题。
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;
});