任务Task
任务在后台使用ThreadPool
在安排需要完成的工作时,任务提供了非常大的灵活性。
可以定义连续的工作,在一个任务完成后需要执行什么工作
可以在层次结构中安排任务,例如父任务可以创建新的子任务,这可以创建一种依赖关系
启动任务
可以使用TaskFactory类或Task类的构造函数和Start()方法
static void TaskMethod()
{
Console.WriteLine("running in a task");
Console.WriteLine("Task id: {0} {1}", Task.CurrentId,
Thread.CurrentThread.ManagedThreadId);
}
// using task factory
TaskFactory tf = new TaskFactory();
Task t1 = tf.StartNew(TaskMethod);
// using the task factory via a task
Task t2 = Task.Factory.StartNew(TaskMethod);
Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
// using Task constructor
Task t3 = new Task(TaskMethod);
// t3.Start();
t3.RunSynchronously();//任务也会启动,但是在调用者的当前线程中运行,调用者需要一直等待该任务结束
Task t4 = new Task(TaskMethod, TaskCreationOptions.PreferFairness);
t4.Start();
TaskCreationOptions选项
设置LongRunning选项,可以通知任务调度器,该任务需要较长时间运行,这样调度器更可能使用新线程
如果该任务关联到父任务上,而父任务取消了,该任务也要取消,此时应设置AttachToParent选项
如果任务应以公平的方式与所有其他任务一起处理,应设置该选项为PerferFairness
连续的任务
通过任务,可以指定在任务完成后,应开始运行另一个特定任务 。例如,一个使用前一个任务的结果的新任务,如果前一个任务失败了,这个任务就应执行一些清理工作
连续处理程序有一个Task类型的参数
static void DoOnFirst()
{
Console.WriteLine("doing some task {0}", Task.CurrentId);
Thread.Sleep(3000);
}
static void DoOnSecond(Task t)
{
Console.WriteLine("task {0} finished", t.Id);
Console.WriteLine("this task id {0}", Task.CurrentId);
Console.WriteLine("do some cleanup");
Thread.Sleep(3000);
}
Task t1 = new Task(DoOnFirst);
Task t2 = t1.ContinueWith(DoOnSecond);
Task t3 = t1.ContinueWith(DoOnSecond);
Task t4 = t2.ContinueWith(DoOnSecond);
连续任务通过在任务上调用ContinueWith( )来定义,也可以使用TaskFactory类来定义
调用DoOnSecond方法的新任务应在任务t1结束时立即启动
在一个任务结束时,可以启动多个任务
连续任务也可以有另一个连续任务
无论前一个任务是如何结束的,连续任务总是在前一个任务结束时启动
使用TaskContinuationOptions枚举中的值,可以指定任务只有在起始任务成功(或失败)结束时启动
Task t5 = t1.ContinueWith(DoOnError, TaskContinuationOptions.OnlyOnFaulted);
任务层次结构
父任务和子任务
一个任务在另一个任务内部创建,构成父子关系
如果父任务在子任务之前结束,父任务的状态就显示为WaitingForChildrenToComplete。子任务也结束时,父任务的状态就显示为RanToCompletion
如果父任务使用TaskCreationOptions枚举中的DetachedFromParent创建子任务,以上就无效
取消父任务就会取消子任务
static void ParentAndChild()
{
Task parent = new Task(ParentTask);
parent.Start();
Thread.Sleep(2000);
Console.WriteLine(parent.Status);
Thread.Sleep(4000);
Console.WriteLine(parent.Status);
}
static void ParentTask()
{
Console.WriteLine("task id {0}", Task.CurrentId);
Task child = new Task(ChildTask);
child.Start();
Thread.Sleep(1000);
Console.WriteLine("parent started child");
// Thread.Sleep(3000);
}
static void ChildTask()
{
Console.WriteLine("child");
Thread.Sleep(5000);
Console.WriteLine("child finished");
}
任务的结果
使用Task类的泛型版本Task,就可以定义返回某个结果的任务的返回类型
为了返回某个结果任务调用的方法可以声明为带任意返回类型
例:有10个线程对一个银行账户取钱,每个线程 进行100次取钱操作。观察同步和不同步情况下的交易状况。
class Account
{
int balance;
Random r = new Random();
public Account(int initial)
{
balance = initial;
}
int withdraw(int amount)
{
Console.WriteLine(Thread.CurrentThread.Name + ":");
if (balance < 0)
{
throw new Exception("余额为负!");
}
lock (this)
{
if (balance >= amount)
{
Console.WriteLine("原有余额:" + balance);
Console.WriteLine("支取余额:-" + amount);
balance -= amount;
Console.WriteLine("现有余额:" + balance);
return amount;
}
else
{
Console.WriteLine("支取不成功");
return 0; //拒绝交易
}
}
public void DoTransactions()
{
//支取随机的金额100次
for (int i = 0; i < 100; i++)
{
try
{
withdraw(r.Next(1, 100));
}
catch (Exception e)
{
Console.WriteLine(e);
}
}
}
static void Main(string[] args)
{
//建立10个线程同时进行交易
Thread[] threads = new Thread[10];
Account acc = new Account(10000);
for (int i = 0; i < 10; i++)
{
Thread t = new Thread(new ThreadStart(acc.DoTransactions));
t.Name = "thread" + i;
threads[i] = t;
}
for (int i = 0; i < 10; i++)
threads[i].Start( );
}
取消同步
int Withdraw(int amount)
{
……
if (balance >= amount)
{
Console.WriteLine("原有余额:" + balance);
Console.WriteLine("支取余额:-" + amount);
balance -= amount;
Console.WriteLine("现有余额:" + balance);
return amount;
}
else
{
Console.WriteLine("支取不成功");
return 0; //拒绝交易
}
}
同步上下文
C#中,只要类从ContextBoundObject继承,并在类定义上增加Synchronization属性标记即可
[Synchronization( )]
public class A: ContextBoundObject
{
//类的成员定义…
}
创建A类对象会自动启动同步
在同一上下文中,任一时刻最多只能有一个线程访问该对象的成员
但这种方式很耗费系统资源,运行速度会有明显下降
只用于对象的每个成员都需要进行同步控制的场合
ReaderWriterLockSlim
为了使锁定机制允许锁定多个读取器(而不是一个写入器)访问某个资源,可以使用ReaderWriterLockSlim
这个类提供了一个锁定功能,如果没有写入器锁定资源,就允许多个读取器访问资源,但只能有一个写入器锁定该资源
ReaderWriterLockSlim类的属性可获得读取阻塞的锁定,如EnterReadLock()和TryEnterReadLock()方法
可以使用EnterWriteLock()和TryEnterWriteLock()方法获得写入锁定
如果任务先读取资源,之后写入资源,就可以使用EnterUpgradableReadLock()和TryEnterUpgradableReadLock()获得可升级的读取锁定。有了这个锁定,就可以获取写入锁定,而无需释放读取锁定
该类的几个属性提供了当前锁定的相关信息,如CurrentReadCount、WaitingReadCount、WaitingUpgradableReadCount和WaitingWriteCount