计算限制异步操作的其他知识-Parallel、PLinQ、Timer(CLR)

笔记大概

  • 闲谈
  • Parallel类
  • Parallel与LINQ
  • Timer类

闲谈

前不久,把CLR via C#的Chapter 26 Computer-Bound Asynchronous Operations内容看完了,接着往下看I/O-Bound Asynchronous Operations,到了Jeffrey介绍Wintellect's .NET Power Threading Library的类库,觉得看得甚是吃力。只好停停脚步,消化消化,回顾下Thread的基础,接着做下笔记,所谓“温故而知新”。

Parallel类

在命名空间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 3

Foreach方法:

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; }
}

Parallel与LINQ

众所周知,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);

Timer类

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。

你可能感兴趣的:(timer)