Task 的异常
- 与Thread不一样,Task可以很方便的传播异常,
- 如果你的task 里边抛出一个未处理的异常(故障) 那么该异常就会重新被抛出,调用了 Wait() 方法
- 调用了Task 的Result属性方法的地方。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Data.SqlClient;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Task task = Task.Run(()=> {
throw null;
});
try
{
task.Wait();
}
catch (AggregateException aex)
{
if (aex.InnerExceptions is NullReferenceException)
{
Console.WriteLine("Null");
}
else
{
throw;
}
}
Console.ReadKey(true);
}
}
}
CLR 将异常包裹在 AggregateException 里,以便在并行编程场景中发挥很好地作用。
Task 的异常
- 如果无需重新抛出异常,通过Task 的 IsFaulted 和 IsCanceled 属性可以检测Task 是否发生了故障。
- 若果两个属性都返回false 那么就没有错误发生。
- 如果IsCanceled 为true, 那就说明OperationCanceledException 为该Task 抛出了。
- 如果IsFaulted 为true, 那么就说明另一个异常被抛出,而Exception 属性也将指明错误。
异常与 “自治” 的Task
- 自治的 设置完就不管了的Task 就是指不通过调用Wait() 方法,Result属性或者 continuation 进行会和任务。
- 针对自治Task 需要向Thread一样, 显示的处理异常, 避免发生 悄无声息的故障。
- 自治的Task上 未处理的异常称为微观察到异常。
未观察到异常
- 可以通过全局 TaskScheduler.UnobservedTaskException 来订阅未观察到异常。
- 关于是 未观察的异常, 有一些细节差别。
- 在Task 发生故障后 ,如果访问Task的 Exception 属性 ,那么就该异常就被认为是已观察到的。
Continuation 继续,延续
- 当结束线程时候,在做点其他事情。
- Continuation 通过回调的方法实现的,当操作一结束就进行执行。
- 在primeNuBerTask 上调用GetAwaiter() 就可以得到一个 awaiter 对象。
- 他的 OnCompeleted方法就会告诉之前的task “当你结束或者发生故障,要执行的委托”
- 可以将Continuation 附加到已经结束的task上 此时Continuation 将会安排立即执行。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Data.SqlClient;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
Task primeNuBerTask = Task.Run(()=>
Enumerable.Range(1, 2000000).Count(ret=>
Enumerable.Range(2,(int)Math.Sqrt(ret)-1).All(i=>ret%i >0)));
var awaiter = primeNuBerTask.GetAwaiter();
awaiter.OnCompleted(()=> {
int result = awaiter.GetResult();
Console.WriteLine(result);
});
Console.ReadKey(true);
}
private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
Console.WriteLine("全局异常处理事件");
}
}
}
awaiter 等待者
- 任何可以暴露下列两个方法和一个属性的对象 就是awaiter :
OnCompleted 和 GetResult - 一个叫做IsCompleted 的 bool 属性
- 没有接口或者父类来统一这些成员,其中 OnCompleted 是 INotifyCompletion 的一部分。
若果发生故障
- 如果之前的任务发生故障, 那么当Continuation 代码调用awaiter。GetResult() 时候,异常就会重新抛出。
- 无需调用GetResult 我们可以直接访问task 的Result属性。
- 但调用GetResult 的好处就是,如果Task发生故障,那么异常就被被直接抛出,而不是包裹在AggreateException 里边, 这样的话catch 块就简洁很多。
非泛型Task
- 针对非泛型task GetResult() 方法有一个void 返回值, 他就是用来重新抛出异常。
同步上下文
- 如果同步上下文出现了,那么OnCompelted会自动捕获他, 并将Continuation 提交到这个上下文中。这一点在富客户端非常有用,因为他会把Continuation 放回到UI线程中。
- 如果是编写一个库,则不希望出现上述行为,应为开销比较大的UI线程切换应该是在程序运行离开库时候只发生一次,而不是出现方法回调,所以,我们可以使用ConfigureAwait 方法来避免这种行为。
- 如果没有同步上下文出现,或者你使用的是ConfigureAwait(false) 那么continuation 会运行在先前task的同一个线程上,从而避免不必要的开销。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Data.SqlClient;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
Task primeNuBerTask = Task.Run(()=>
Enumerable.Range(1, 2000000).Count(ret=>
Enumerable.Range(2,(int)Math.Sqrt(ret)-1).All(i=>ret%i >0)));
var awaiter = primeNuBerTask.ConfigureAwait(false).GetAwaiter();
awaiter.OnCompleted(()=> {
int result = awaiter.GetResult();
Console.WriteLine(result);
});
Console.ReadKey(true);
}
private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
Console.WriteLine("全局异常处理事件");
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Data.SqlClient;
using System.Xml;
///For Text Two
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
TaskScheduler.UnobservedTaskException += TaskScheduler_UnobservedTaskException;
Task primeNuBerTask = Task.Run(()=>
Enumerable.Range(1, 2000000).Count(ret=>
Enumerable.Range(2,(int)Math.Sqrt(ret)-1).All(i=>ret%i >0)));
primeNuBerTask.ContinueWith(retTask =>
{
int retsult = retTask.Result;
Console.WriteLine(retsult);
});
Console.ReadKey(true);
}
private static void TaskScheduler_UnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs e)
{
Console.WriteLine("全局异常处理事件");
}
}
}
ContinueWitch
- 另外一种附加Continuation 的方式就是调用task的 ContinueWith 方法
- ContinueWith 本身就返回一个task 他可以用他来附加更多的Continuation ,但是必须直接处理AggregateException
- 如果task发生故障,需要写额外的代码来吧Continuation封装到UI上。
- 在非UI上下中,如果想让Continuation 和task 执行在同一个线程上,就必须指定taskContinuationOptions。ExecuteSynchronouly, 否则他将弹回到线程池。
TaskCompletionSource
- Task.Run 创建Task。 另外一种方式就是用TaskCompletionSource 来创建Task
- TaskCompletionSource 让你稍后开始和结束的任意操作中创建Task。
- 他会为你提供一个可手动执行的 从属Task ,指示操作何时发生何时结束。
- 他对IO-Bound 类工作比较理想。可以获取所有task的好处。(传播值 , 异常 ,Continuation等)。 不需要在操作时阻塞线程。
使用 TaskCompletionSource
- 初始化一个实例即可。他有一个Task 属性可以 返回一个Task。
- 该 Task 完全由TaskCompletionSource 对象控制。
- 调用任意一个方法就会给Task发信号: 完成,故障,取消。
- 这些方法只能调用一次,如果再次调用就会抛出异常。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Data.SqlClient;
using System.Xml;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
TaskCompletionSource tcs = new TaskCompletionSource();
new Thread(() =>
{
Thread.Sleep(5000);
tcs.SetResult(42);
})
{ IsBackground = true }.Start();
Task<int> task = tcs.Task;
Console.WriteLine(task.Result);
Console.ReadKey(true);
}
}
}
for text two
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Data.SqlClient;
using System.Xml;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
Task task = Run(() => {
Thread.Sleep(5000);
return 10;
});
Console.WriteLine(task.Result);
Console.ReadKey(true);
}
static Task<TResult> Run<TResult>(Func<TResult> func)
{
TaskCompletionSource<TResult> task = new TaskCompletionSource<TResult>();
new Thread(() => {
try
{
task.SetResult(func());
}
catch (Exception ex )
{
task.SetException(ex);
}
}).Start();
return task.Task;
}
}
internal class TResult
{
}
}
TaskCompletionSource的真正作用
- 他创建Task 但并不占用线程。
for text One Timer
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using System.Threading;
using System.Data.SqlClient;
using System.Xml;
namespace ConsoleApp3
{
class Program
{
static void Main(string[] args)
{
var awaiter = GetAnswerToLife().GetAwaiter();
awaiter.OnCompleted(() => {
Console.WriteLine(awaiter.GetResult());
});
Console.ReadKey(true);
}
static Task<int> GetAnswerToLife()
{
var tcs = new TaskCompletionSource<int>();
var timer = new System.Timers.Timer(5000) { AutoReset = false };
timer.Elapsed += delegate { timer.Dispose();tcs.SetResult(40); };
timer.Start();
return tcs.Task;
}
}
}
for text Delay
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace ConsoleApp4
{
class Program
{
static void Main(string[] args)
{
Delay(1000).ContinueWith(retTask => {
Console.WriteLine(retTask.IsFaulted);
});
Console.ReadKey();
}
static Task Delay(int millisecond)
{
TaskCompletionSource<object> tsc = new TaskCompletionSource<object>();
var timer = new System.Timers.Timer(3000) { AutoReset=false};
timer.Elapsed += delegate { timer.Dispose();tsc.SetResult(null); };
timer.Start();
return tsc.Task;
}
}
}