1、从Thread到Task
创建线程的代价高昂,而且每个线程都要占用大量虚拟内存(例如Windows默认1MB)。在.Net Framework 4及后续版本中,异步操作不是每次创建一个线程,而是创建一个Task,并告诉任务调度器有异步工作要执行。此时调度器可能采取多种策略,但默认是从线程池请求一个工作线程,线程池会自行判断怎么做最高效。
任务是对象,其中封装了以异步方式执行的工作。Task与委托的区别:委托是同步的,而任务是异步的。执行委托的时候,当前线程的控制点会立即转移到委托的代码。除非委托结束,否则控制不会返回调用者。而启动任务,控制几乎立即返回调用者,无论任务要执行多少工作。总而言之:任务将委托从同步执行模式转变为异步。
代码示例如下:
#region 3、Task异步任务
public static void Main1()
{
Task task = Task.Run(() =>
{
for (int count = 0; count < _Repetitions; count++)
{
Console.Write('-');
}
}
);
Task task2 = Task.Run(() =>
{
for (int count = 0; count < _Repetitions; count++)
{
Console.Write('q');
}
}
);
//直到task任务完成
//调用Wait强迫主线程等待分配给任务的所有工作完成,相当于调用线程的Join()操作
//task.Wait();
//等待一组任务执行完成,当然,目前只有一个任务
// Task.WaitAll(task,task2);
//等待其中一个执行完成
Task.WaitAny(task, task2);
for (int count = 0; count < _Repetitions; count++)
{
Console.Write('+');
}
Console.ReadKey();
// task.Wait();
}
#endregion
#region 任务延续
public static void Main()
{
Console.WriteLine("Before");
Task taskA = Task.Run(() =>
Console.WriteLine("Starting...")
).ContinueWith(antecedent=>Console.WriteLine("Continue A...."));
Task taskB = taskA.ContinueWith(antecedent => Console.WriteLine("Continuing B....."));
Task taskC = taskA.ContinueWith(antecedent => Console.WriteLine("Contiuing C..."));
Task.WaitAll(taskB,taskC);
Console.WriteLine("Finished!");
Console.ReadKey();
}
#endregion
2、Task上异常处理
在同步调用的场景下,异常处理可以直接包裹在try代码块中,用catch子句告诉编译器发生异常时应执行什么代码,但在异步的场景下不能这么做,不能用try包装Start()调用来捕捉异常,因为控制会立即从调用返回,然后控制会离开try块。一个解决方案是将任务的委托主体包装到try/catch代码块中。
#region 异常处理
public static void Main_1()
{
Task task = Task.Run(() =>
{
throw new InvalidOperationException();
});
try
{
task.Wait();
}
catch (AggregateException exception)
{
exception.Handle(eachException =>
{
Console.WriteLine($"开始打印ERROR:{eachException.Message}");
return true;
});
}
}
public static void Main_2()
{
bool parentTaskFaulted = false;
Task task = Task.Run(() =>
{
throw new InvalidOperationException();
});
Task continuationTask = task.ContinueWith((antecedentTask) => {
parentTaskFaulted = antecedentTask.IsFaulted;
},TaskContinuationOptions.OnlyOnFaulted);
task.Start();
continuationTask.Wait();
Trace.Assert(parentTaskFaulted);
Trace.Assert(task.IsFaulted);
task.Exception.Handle(eachException =>
{
Console.WriteLine($"开始打印ERROR:{eachException.Message}");
return true;
});
}
//处理Thread上的未处理异常
public static Stopwatch clock = new Stopwatch();
public static void Main()
{
try
{
clock.Start();
AppDomain.CurrentDomain.UnhandledException += (s, e) =>
{
Message("Event handler starting");
Delay(4000);
};
Thread thread = new Thread(() =>
{
Message("Throwing exception ");
throw new Exception();
});
thread.Start();
Delay(2000);
}
finally
{
Message("Finally block running.");
}
}
static void Delay(int i)
{
Message($"Sleeping for {i} ms");
Thread.Sleep(i);
Message("Awwake");
}
static void Message(string text)
{
Console.WriteLine("{0}:{1:0000}:{2}",
Thread.CurrentThread.ManagedThreadId, clock.ElapsedMilliseconds,text);
}
#endregion
执行结果如下:
当然在实际代码中需要注意一下3点:
1、避免程序在任何线程上产生未处理异常
2、考虑登记“未处理异常”事件处理程序以进行调试、记录和紧急关闭。
3、要取消未完成的任务而不要在程序关闭期间允许其运行。