C#并行编程-Task(很重要的参考资料)
其实Task跟线程池ThreadPool的功能类似,不过写起来更为简单,直观。代码更简洁了,使用Task来进行操作。可以跟线程一样可以轻松的对执行的方法进行控制。
Task是.net4.0中的一个新特性,提供的功能非常强大。一个Task表示一个异步操作,Task提供了很多方法和属性,通过这些方法和属性能够对Task的执行进行控制,并且能够获得其状态信息。
Task的创建和执行都是独立的,因此可以对关联操作的执行拥有完全的控制权。
使用Parallel.For、Parallel.ForEach的循环迭代的并行执行,TPL会在后台创建System.Threading.Tasks.Task的实例。
使用Parallel.Invoke时,TPL也会创建与调用的委托数目一致的System.Threading.Tasks.Task的实例。
Task任务的状态
static void Main(string[] args)
{
//Created:表示默认初始化任务,但是我们发现“工厂创建的”实例直接跳过。
//WaitingToRun: 这种状态表示等待任务调度器分配线程给任务执行。
//RanToCompletion:任务执行完毕。
Task t1 = new Task(() => Program.ExecAction());
Console.Write(t1.Status);//输出:Created
t1.Start();
Console.Write(t1.Status);//输出:WaitingToRun
Task t2 = Task.Factory.StartNew(() => Program.ExecAction());
Console.Write(t2.Status); //输出:WaitingToRun
Task t3 = Task.Run(() => Program.ExecAction());
Console.Write(t3.Status);//输出:WaitingToRun
t3.ContinueWith(r => Console.Write(t3.Status));//输出:RanToCompletion
Console.ReadKey();
}
~
namespace TestApp.Controllers
{
public delegate string Mydelegate();
public class HomeController : Controller
{
public ActionResult Index()
{
//特别注意,Task是接收不到返回值的,只有Task<TResult>才能接收到返回值(即:t1是没有Result属性的)
//Task t1 = new Task(() => Console.WriteLine("启动了"));
//启动一个任务的方法一:(需要调用Start方法启动任务)
Task t1 = new Task(() => Test.ExecuteFun());
//t1.RunSynchronously();//运行在主线程中,等同于直接运行:Test.ExecuteFun()因此,该Task不是运行在线程池中,而是运行在主线程中
t1.Start(); //启动 Task,并将它安排到当前的 TaskScheduler 中执行。
//启动一个任务的方法二:(就会立即启动任务)
Task<string> t2 = Task.Factory.StartNew<string>(() => Test.GetUserName());
//这里不能再次Start()了,因为StartNew就已经Start()了
var userNameA = t2.Result; //得到“刘德华”
//Task.Run的跟Task.Factory.StarNew和new Task相差不多,不同的是前两种是放进线程池立即执行,而Task.Run则是等线程池空闲后再执行。//启动一个任务的方法三:(就会立即启动任务)
Task<string> t3 = Task.Run<string>(() => Test.GetUserName());//Task.Run方法是Task类中的静态方法,接受的参数是委托。返回值是为该Task对象。
//这种方式是直接运行了Task,不像第一种方法那样还需要Start();
var userNameB = t3.Result; //得到“刘德华”
//----------------------------------------------------//
//Task类型公开了多个ContinueWith的重载。当另外一个task完成的时候,该方法会创建新的将被调度的task。该重载接受CancellationToken,TaskCreationOptions,和TaskScheduler,这些都对task的调度和执行提供了细粒度的控制。
//TaskFactory类提供了ContinueWhenAll 和ContinueWhenAny方法。当提供的一系列的tasks中的所有或任何一个完成时,这些方法会创建一个即将被调度的新的task。有了ContinueWith,就有了对于调度的控制和任务的执行的支持。
//一下表达式:表示在执行完t3这个任务后在执行Task.Run<int>(()=>Test.GetAge())这个任务【ContinueWith其实就相当于回调】
var userAge = t3.ContinueWith(r => Task.Run<int>(() => Test.GetAge())).Result.Result; //得到“23”
return View();
}
}
public class Test
{
public static void ExecuteFun()
{
Console.WriteLine("哈哈");
}
public static int GetAge()
{
return 23;
}
public static string GetUserName()
{
return "刘德华";
}
}
}
~
public static Task<int> CallFuncAsync()
{
//Func<int> c = () => { return 3; };
//return Task.Run<int>(c); //这种写法,
//return Task.Run<int>(() => Myfun());//或者下面这种写法
return Task.Run<int>(() => { return 3; }); //或者这种写法
}
public static int Myfun()
{
return 3;
}
Task异常捕获
public class HomeController : Controller
{
/// <summary>
/// 调用 Task 的 Wait 方法时使用 try-catch 捕获异常:
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
Task t = Task.Run(() => TestException());
try
{
//和线程不同,Task中抛出的异常可以捕获,但是也不是直接捕获,而是由调用Wait()方法或者访问Result属性的时候,由他们获得异常,将这个异常包装成AggregateException类型,再重新抛出捕获。
//默认情况下,Task任务是由线程池线程异步执行。要知道Task任务的是否完成,可以通过task.IsCompleted属性获得,也可以使用task.Wait来等待Task完成。
//Wait方法表示:等待Task任务执行完成(Wait会阻塞当前线程)
t.Wait();
}
catch (Exception ex)
{
var a = ex.Message; //a的值为:发生一个或多个错误。
var b = ex.GetBaseException(); //b的值为:Task异常测试
}
return View();
}
static void TestException()
{
throw new Exception("Task异常测试");
}
}
2
public class HomeController : Controller
{
/// <summary>
/// ContinueWith的使用:
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
GetVal(6, 0); //0是不能作为被除数的,所以会引发错误
return View();
}
private int Sum(int x, int y)
{
return x / y;
}
public void GetVal(int x, int y)
{
Task<string> t = Task.Run<string>(() => { return Sum(x, y).ToString(); });
//ContinueWith方法是完成时异步执行的延续任务,这个延续认为是带一个参数的WriteLlog方法
t.ContinueWith(r => { WriteLog("异常信息:" + t.Exception.InnerException.StackTrace); }, TaskContinuationOptions.OnlyOnFaulted);
//askContinuationOptions.OnlyOnFaulted表示:指定只应在延续任务前面的任务引发了未处理异常的情况下才安排延续任务。 此选项对多任务延续无效。【即:只有在发生异常的情况下才将异常信息记录到日志】
}
static void WriteLog(string logStr)
{
System.IO.File.AppendAllText(@"D:\Log.txt", logStr + "\r\n");
}
}
3
class Program
{
static void Main(string[] args)
{
GetStringAsync();
}
public static void GetStringAsync()
{
try
{
HttpClient hc = new HttpClient();
var task1 = hc.GetStringAsync("http://www.1111.com");
var task2 = hc.GetStringAsync("http://www.2222.com");
var task3 = hc.GetStringAsync("http://www.3333.com");
Task.WaitAll(task1, task2, task3);
Console.WriteLine("执行成功");
}
//AggregateException类表示应用程序执行期间发生的一个或多个错误。(即:所有的内部异常)
catch (AggregateException ae)
{
//InnerExceptions表示获取导致当前异常的System.Exception实例的只读集合。
foreach (Exception item in ae.InnerExceptions)
{
Console.WriteLine(item.Message);
}
Console.ReadKey();
}
}
}
异步执行的延续任务:ContinueWith方法
public class HomeController : Controller
{
/// <summary>
/// ContinueWith的使用:
/// </summary>
/// <returns></returns>
public ActionResult Index()
{
GetVal(5, 6);
return View();
}
private int Sum(int x, int y)
{
return x + y;
}
public void GetVal(int x, int y)
{
Task<string> t = Task.Run<string>(() => { return Sum(x, y).ToString(); });
//ContinueWith方法是完成时异步执行的延续任务,这个延续认为是带一个参数的WriteLlog方法
t.ContinueWith(r => { WriteLlog(t.Result); }); //将Sum方法的返回结果写入日志
}
static void WriteLlog(string logStr)
{
System.IO.File.AppendAllText(@"D:\Log.txt", logStr + "/r/n");
}
}
Task任务的取消
使用CancellationTokenSource对象需要与Task对象进行配合使用,Task会对当前运行的状态进行控制(这个不用我们关心是如何孔控制的)。而CancellationTokenSource则是外部对Task的控制,如取消、定时取消。
class Program
{
static void Main(string[] args)
{
//通知 System.Threading.CancellationToken,告知其应被取消。
var TokenSource = new CancellationTokenSource();
//获取与此 System.Threading.CancellationTokenSource 关联的 System.Threading.CancellationToken。
var Token = TokenSource.Token;
var task = Task.Factory.StartNew(() =>
{
for (var i = 1; i < 1000; i++)
{
Thread.Sleep(100);
//判断是否取消任务,取消为true;不取消为false
if (Token.IsCancellationRequested)
{
Console.Write("取消任务成功!");
return;
}
}
},Token);
//在这里还可以给token注册了一个方法,输出一条信息 用户输入0后,执行tokenSource.Cancel方法取消任务。这个取消任务执行后,会执行这个代码.
Token.Register(() => Console.WriteLine("取消"));
//等待用户输入
var input = Console.ReadLine();
if (Convert.ToInt32(input) == 0)
{
//如果输入了0,则取消这个任务;
//一旦cancel被调用。task将会抛出OperationCanceledException来中断此任务的执行,最后将当前task的Status的IsCanceled属性设为true
TokenSource.Cancel();
}
Console.ReadKey();
}
}
多个 CancellationTokenSource 复合
在有多个CancellationTokenSource需要一起并行管理的时候,比如任意一个任务取消 则取消所有任务。我们不必去一个一个的去关闭,只需要将需要一起并行关闭的CancellationTokenSource组合起来就行了。如下代码所示,执行结果是跟上面的图一样的。我就不再上图了。
class Program
{
//声明CancellationTokenSource对象
static CancellationTokenSource c1 = new CancellationTokenSource();
static CancellationTokenSource c2 = new CancellationTokenSource();
static CancellationTokenSource c3 = new CancellationTokenSource();
//使用多个CancellationTokenSource进行复合管理
static CancellationTokenSource compositeCancel = CancellationTokenSource.CreateLinkedTokenSource(c1.Token, c2.Token, c3.Token);
//主线程方法
static void Main(string[] args)
{
var task = Task.Factory.StartNew(() =>
{
for (var i = 1; i < 1000; i++)
{
Thread.Sleep(100);
//判断是否取消任务,取消为true;不取消为false
if (compositeCancel.IsCancellationRequested)
{
Console.Write("取消任务成功!");
return;
}
}
}, compositeCancel.Token);
//等待用户输入
var input = Console.ReadLine();
//如果输入了0,则取消这个任务;
if (Convert.ToInt32(input) == 0)
{
//任意一个 CancellationTokenSource 取消任务,那么所有任务都会被取消。
c1.Cancel();
}
Console.ReadKey();
}
}
以上代码调用了c1.Cancel();
触发了匿名方法中的compositeCancel.IsCancellationRequested
为true,则取消了任务。所以我们在所有任务中判断复合的这个CancellationTokenSource对象即可。
Task的同步执行
class Program
{
static void Main(string[] args)
{
//RunSynchronously()表示同步执行,也就是阻塞式执行
var t = new Task(() =>
{
Console.WriteLine("我是工作线程:TaskId={0}", Thread.CurrentThread.ManagedThreadId);
});
t.RunSynchronously();
Console.WriteLine("我是主线程:TaskId={0}", Thread.CurrentThread.ManagedThreadId);
Console.ReadKey();
//我们可以看到使用了RunSynchronously()后,执行工作子任务的是主线程
}
}