前不久,把CLR via C#的Chapter 26 Computer-Bound Asynchronous Operations内容看完了,接着往下看I/O-Bound Asynchronous Operations,到了Jeffrey介绍Wintellect's .NET Power Threading Library的类库,觉得看得甚是吃力。只好停停脚步,消化消化,回顾下Thread的基础,接着做下笔记,所谓“温故而知新”。
在命名空间System.Threading.Tasks下有个静态类Parallel。Parallel提供了三个方法For,ForEach,Invoke来简化在同步状态下的Task操作。在《改善C#程序的的157建议》中就推荐使用Parallel来简化Task。
简单例子如下,
For方法:
class Program
{static void Main(string[] args){int[] nums = new int[] { 1, 2, 3, 4 };Parallel.For(0, nums.Length, (i) =>{Console.WriteLine("Task {0}", i);
Thread.Sleep(500);});Console.ReadKey();}}
输出可能为:
Task 0
Task 2
Task 1
Task 3Foreach方法:
class Program
{static void Main(string[] args){int[] nums = new int[] { 1, 2, 3, 4 };Parallel.ForEach(nums, (i) =>{Console.WriteLine("Task {0}", i);
Thread.Sleep(500);});Console.ReadKey();}}
输出可能为:
Task 1
Task 3
Task 2
Task 4
Jeffrey建议:在可以使用For或Foreach方法其一时,推荐使用For。
Invoke方法:
static void Main(string[] args){int i = 0;
Parallel.Invoke(() => { Console.WriteLine("Task {0}", i++); Thread.Sleep(1000); },
() => { Console.WriteLine("Task {0}", i++); Thread.Sleep(500); },
() => { Console.WriteLine("Task {0}", i++); Thread.Sleep(500); },
() => { Console.WriteLine("Task {0}", i++); Thread.Sleep(500); }
);Console.ReadKey();}
输出可能为:
Task 0
Task 1
Task 2
Task 3需要注意下的是,Parallel的For,ForEach和Invoke方法都是可以接受一个参数:ParallelOptions。定义如下:
public class ParallelOptions{public ParallelOptions();
//Allows cancellation of operation
public CancellationToken CancellationToken { get; set; } //Default = CancellationToken.None//Allows you to specify the maximum number of work item
//that can be operated on concurrently
public Int32 MaxDegreeOfParallelism { get; set; } //Default = -1 (# of available CPUs)//Allows you to specify which TaskScheduler to use
public TaskScheduler TaskScheduler { get; set; } //Default = TaskScheduler.Default}另外,For和Foreach方法可以调用三个委托:
- The task local initialzation delegate(localinit):初始化Task时调用。
- The body delegate:Task执行的Action。
- The task local finally delegate:Task每次执行完Action后调用。
Copy个Jeffrey的例子:
private static Int64 DirectoryBytes(string path){var files = Directory.EnumerateFiles(path);Int64 masterTotal = 0;ParallelLoopResult result = Parallel.ForEach<string, Int64>(files,
() =>{return 0;
},(file, loopState, index, taskLocalTotal) =>{Int64 fileLength = 0;FileStream fs = null;
try
{fs = File.OpenRead(file);fileLength = fs.Length;}catch (IOException) { }
finally
{if (fs != null){fs.Dispose();}}return taskLocalTotal + fileLength;
},(taskLocalTotal) =>{Interlocked.Add(ref masterTotal, taskLocalTotal);
});return masterTotal;
}Interlocked.Add方法确保了线程安全。
Body delegate参数中的loopState是ParallelLoopState类型。ParallelLoopState可以管理Parallel的For方法和Foreach方法的具体实现及获得当前的Loop的状态。ParallelLoopState定义如下:
public class ParallelLoopState{public void Stop();public Boolean IsStopped { get; }
public void Break();public Int64? LowestBreakInteration { get; }
public Boolean IsException { get; }
public Boolean ShouldExitCurrentInteration { get; }
}Parallel的For方法和Foreach方法都会返回ParallelLoopResult引用,ParallelLoopResult定义如下:
public struct ParallelLoopResult{public Boolean IsCompleted { get; }
public Int64? LowestBreakIteration { get; }
}
众所周知,Microsoft’s Language Integrated Query(LINQ)提供了简单的语法来查询数据集合。一般的是,只有一个线程操作这个查询。想要多线程参与,就会使用到Parallel LINQ。System.Linq.ParallelEnumerable类实现了Parallel LINQ功能,该类暴露的几个基本LINQ方法扩展了System.Linq.ParallelQurey<T>类型:Where、Select、SelectMany、GroupBy、Join、OrderBy、Skip、Take等等。使用以上方法时,你必须使用ParallelEnumerable‘s AsParallel的扩展方法将(IEnumerable / IEnumerable<T>)转换成(ParallelQurey / ParallelQurey<T>).
public static ParallelQuery<TSource> AsParallel<TSource>(this IEnumerable<TSource> source);public static ParallelQuery AsParallel(this IEnumerable source);假如,想把(ParallelQurey / ParallelQurey<T>)转换成(IEnumerable / IEnumerable<T>)从而实现单线程实行查询,就用到ParallelEnumerable’s AsSequential方法:
public static IEnumerable<TSource> AsSequential(this ParallelQurey<TSource> source);
foreach方法是提供一个线程来遍历一个query的结果,如果想要使用parallel方式遍历,应该使用ParallelEnumerable’s ForAll方法:
static void ForAll<TSource>(this ParallelQuery<TSource> , Action<TSource> action);
ParallelLINQuery方法利用多个线程,这样就会导致输出结果无序。但你想要保障输出结果的有序性,可以使用ParallelEnumerable’s AsOrder方法,事物都是双面的,这样会到时性能的降低。
Parallel LINQ还提供了额外的方法:
public static ParallelQuery<TSource> WithCancelation<TSource>(this ParallelQuery<TSource> source, CancellationToken cancellationToken);public static ParallelQuery<TSource> WithDegreeOfParallelism<TSource>(this ParallelQuery<TSource> source, Int32 degreeOfParallelism);public static ParallelQuery<TSource> WithExecutionMode<TSource>(this ParallelQuery<TSource> source, ParallelExecutionMode executionMode);public static ParallelQuery<TSource> WithMergeOptions<TSource>(this ParallelQuery<TSource> source, ParallelMergeOptions mergeOptions);
System.Threading提供了Timer类型,该类成周期地使线程池执行一个方法。Timer提供四个构造函数:
public sealed class Timer : MarshalByRefObject, IDisposable{public Timer(TimerCallback callback, object state, Int32 dueTime, Int32 period);public Timer(TimerCallback callback, object state, UInt32 dueTime, UInt32 period);public Timer(TimerCallback callback, object state, Int64 dueTime, Int64 period);public Timer(TimerCallback callback, object state, TimeSpan dueTime, TimeSpan period);}System.Threading.TimerCallback的委托类型定义如下:
delegate void TimerCallback(object state);
构造函数的state参数允许你向callback传入数据,不需要可为null。duetime是指第一次执行callback需要等待duetime,最后个参数period指多少时间周期调用callback,当为Timerout.Infinite(-1)时,则只调用一次。
Jeffrey建议:使用Timerout.Infinite(-1)初始化Timer,并在callback方法中使用Change方法,依旧指定period为Timerout.Infinite(-1)。原因:在线程池中刚开始只有一个线程执行所有的Timer,当callback执行的时间超过period时,就会开启另一个线程执行新的callback,这时候会引起多线程带来的Exceptions。
Change方法定义如下:
public sealed class Timer : MarshalByRefObject, IDisposable{public Boolean Change(Int32 dueTime, Int32 period);
public Boolean Change(UInt32 dueTime, UInt32 period);
public Boolean Change(Int64 dueTime, Int64 period);
public Boolean Change(TimeSpan dueTime, TimeSpan period);
}Timer实现接口IDisposable定义如下:
public sealed class Timer : MarshalByRefObject, IDisposable{public Boolean Dispose();
public Boolean Dispose(WaitHandle notifyObject);
}Coding:
static void Main(string[] args){Console.WriteLine("Main Thread : starting a timer.");
using (timer = new Timer(TimerMethod, 10, 2000, Timeout.Infinite)){Console.WriteLine("Main Thread: Doing other work...");
Thread.Sleep(10000);}}private static void TimerMethod(object state){Console.WriteLine("In Timer: state = {0}", state);
Thread.Sleep(1000);timer.Change(2000, Timeout.Infinite);}
关于详细内容,请见《CLR via C#》Chapter 26 Computer-Bound Asynchronous Operations。