1.同步方法中捕捉异常肯定是可以的
2.Task方法内部try…catch可以捕捉异常
首先,线程内部出现异常,所以首选处理方式是在Task中使用try…catch…把异常处理掉
3.Task方法内部异常未处理(异常消失在外太空。),外部如何捕捉
try…catch…无法直接捕获Task内部异常:
如下写法,无法捕捉Task内部异常。
try
{
var task = new Task(() =>
{
throw new CustomException("task内部报错了");
});
task.Start();
}
catch(Exception e)
{
}
但是我们可以通过调用Task.Wait(), Result方法,ContinueWith捕捉Task内部的异常。
线程异常的捕获方法有阻塞型捕获和异步捕获两种。
阻塞型捕获异常就是:当一个或多个线程启动后,我们就一直等待,等到所有线程执行完成,判断这些线程中是否出现异常,而后再执行后续的代码。
异步型捕获异常就是:一个线程启动后,我们不去等待他执行完成,而知直接执行后续其他代码。当线程出现异常时,会自动返回异常信息,或者在需要时主动去获取线程异常信息。
①Wait(),Result, GetAwaiter().GetResult() (阻塞型捕获)
Task中抛出的异常可以捕获,但是也不是直接捕获,而是由调用Wait()方法或者访问Result属性的时候获得异常,优先以AggregateException类型抛出异常,如果没有AggregateException异常捕获的话则以Exception抛出异常。 GetAwaiter().GetResult()方法以Exception抛出异常。
测试案例1:
#region 通过Wait捕获异常
/// <summary>
/// 通过wait可以捕获Task内部的异常
/// </summary>
public static void WaitException()
{
try
{
//和线程不同,Task中抛出的异常可以捕获,但是也不是直接捕获,而是由调用Wait()方法或者访问Result属性的时候,由他们获得异常,将这个异常包装成AggregateException类型,或者直接以Exception,抛出捕获。
//默认情况下,Task任务是由线程池线程异步执行。要知道Task任务的是否完成,可以通过task.IsCompleted属性获得,也可以使用task.Wait来等待Task完成。
Task t = Task.Run(() => TestException());
t.Wait();
}
catch (Exception ex)
{
var a = ex.Message; //a的值为:发生一个或多个错误。
var b = ex.GetBaseException(); //b的值为:Task异常测试
Console.WriteLine(a + "|*|" + b);
}
}
static void TestException()
{
throw new Exception("Task异常测试");
}
#endregion
测试结果:
②但是如果没有返回结果,或者不想调用Wait()方法,该怎么获取异常呢?使用ContinueWith捕获异常(异步型捕获)(推荐)
测试案例2:
#region 通过ContinueWith设置TaskContinuationOptions参数来捕获异常(推荐)
public static void ContinueWithException(int x, int y)
{
Task<string> t = Task.Run<string>(() =>
{
Thread.Sleep(300);
Console.WriteLine("我是线程还在异步执行");
return Sumt(x, y).ToString();
});
//NotOnFaulted表示如果没有异常,才会执行ContinueWith内部的代码,但此时线程不会阻塞
//t.ContinueWith(r =>
//{
// string Exception = Convert.ToString(t.Exception);
// Console.WriteLine("异常信息1:" + Exception);
//}, TaskContinuationOptions.NotOnFaulted);
//Console.WriteLine("继续异步执行1");
//OnlyOnFaulted表示如果有异常,才会执行ContinueWith内部的代码,但此时线程不会被阻塞
t.ContinueWith(r =>
{
//Thread.Sleep(3000);
string Exception = Convert.ToString(t.Exception);
Console.WriteLine("异常信息2:" + Exception);
}, TaskContinuationOptions.OnlyOnFaulted);
Console.WriteLine("继续异步执行2");
//askContinuationOptions.OnlyOnFaulted表示:指定只应在延续任务前面的任务引发了未处理异常的情况下才安排延续任务。 此选项对多任务延续无效。【即:只有在发生异常的情况下才将异常信息记录到日志】
}
private static int Sumt(int x, int y)
{
return x / y;
}
#endregion
测试结果:
上面使用起来比较麻烦,添加一个扩展方法:
AggregateException捕获多线程中所有异常。AggregateException是一个集合。
public static Task Catch(this Task task)
{
return task.ContinueWith<Task>(delegate(Task t)
{
if (t != null && t.IsFaulted)
{
AggregateException exception = t.Exception;
Trace.TraceError("Catch exception thrown by Task: {0}", new object[]
{
exception
});
}
return t;
}).Unwrap();
}
public static Task<T> Catch<T>(this Task<T> task)
{
return task.ContinueWith<Task<T>>(delegate(Task<T> t)
{
if (t != null && t.IsFaulted)
{
AggregateException exception = t.Exception;
Trace.TraceError("Catch<T> exception thrown by Task: {0}", new object[]
{
exception
});
}
return t;
}).Unwrap<T>();
}
③全局捕获Task中未观察到的异常
TaskScheduler.UnobservedTaskException += (object sender, UnobservedTaskExceptionEventArgs e)=> {
Console.WriteLine("捕获异常,"+e.Exception.InnerException.Message);
};
测试案例3: 把测试案例1中的 t.Wait()方法注释掉,看能不能被全局捕捉。
测试结果:
4.异步方法中捕捉异常
①async…await可以捕捉异常
try{
await task1;
}
catch{
}
②C# 异步方法,尽量避免使用async void而是要用async Task
async void 方法引发的任何异常都会直接在 SynchronizationContext(在 async void 方法启动时处于活动状态)上引发,无法捕获从 async void 方法引发的异常。,从而引发程序崩溃。
下面代码无法使用 Catch 捕获来自 Async Void 方法的异常
private async void ThrowExceptionAsync()
{
throw new InvalidOperationException();
}
public void AsyncVoidExceptions_CannotBeCaughtByCatch()
{
try
{
ThrowExceptionAsync();
}
catch (Exception)
{
// The exception is never caught here!
throw;
}
}
可以通过对 GUI/ASP.NET 应用程序使用 AppDomain.UnhandledException 或类似的全部捕获事件观察到这些异常,但是使用这些事件进行常规异常处理会导致无法维护。
Async void 方法具有不同的组合语义。 返回 Task 或 Task<T> 的 async 方法可以使用 await、Task.WhenAny、Task.WhenAll 等方便地组合而成。 返回 void 的 async 方法未提供一种简单方式,用于向调用代码通知它们已完成。 启动几个 async void 方法不难,但是确定它们何时结束却不易。 Async void 方法会在启动和结束时通知 SynchronizationContext,但是对于常规应用程序代码而言,自定义 SynchronizationContext 是一种复杂的解决方案。
Async void 方法难以测试。 由于错误处理和组合方面的差异,因此调用 async void 方法的单元测试不易编写。 MSTest 异步测试支持仅适用于返回 Task 或 Task<T> 的 async 方法。 可以安装 SynchronizationContext 来检测所有 async void 方法都已完成的时间并收集所有异常,不过只需使 async void 方法改为返回 Task,这会简单得多。
显然,async void 方法与 async Task 方法相比具有几个缺点,但是这些方法在一种特定情况下十分有用: 异步事件处理程序。 语义方面的差异对于异步事件处理程序十分有意义。 它们会直接在 SynchronizationContext 上引发异常,这类似于同步事件处理程序的行为方式。 同步事件处理程序通常是私有的,因此无法组合或直接测试。 我喜欢采用的一个方法是尽量减少异步事件处理程序中的代码(例如,让它等待包含实际逻辑的 async Task 方法)。 下面的代码演示了这一方法,该方法通过将 async void 方法用于事件处理程序而不牺牲可测试性:
private async void button1_Click(object sender, EventArgs e)
{
await Button1ClickAsync();
}
public async Task Button1ClickAsync()
{
// Do asynchronous work.
await Task.Delay(1000);
}