ITPub博客

首页 > IT职业 > IT图书 > C#基础提升系列——C#任务和并行编程

C#基础提升系列——C#任务和并行编程

IT图书 作者:jmj83 时间:2021-09-09 09:39:58 0 删除 编辑
ParallelLoopState.Break()方法的示例如下:
public static void StopParallelForEarly()
{
    ParallelLoopResult result = Parallel.For(0, 10,
        (int i, ParallelLoopState pls) =>
        {
            log("开始:" + i);
            if (i > 5)
            {
                pls.Break();
                log("中断:" + i);
            }
            Task.Delay(5000).Wait();
            log("结束:" + i);
        });
    Console.WriteLine("循环是否完成运行:" + result.IsCompleted);
    Console.WriteLine("最小迭代索引:" + result.LowestBreakIteration);
}

上述执行结果如下:

开始:0         任务ID: 1       线程ID: 1
开始:2         任务ID: 2       线程ID: 3
开始:4         任务ID: 3       线程ID: 5
开始:6         任务ID: 4       线程ID: 4
中断:6         任务ID: 4       线程ID: 4
开始:8         任务ID: 5       线程ID: 6
中断:8         任务ID: 5       线程ID: 6
开始:1         任务ID: 6       线程ID: 8
开始:3         任务ID: 7       线程ID: 9
开始:5         任务ID: 8       线程ID: 10
结束:2         任务ID: 2       线程ID: 3
结束:4         任务ID: 3       线程ID: 5
结束:0         任务ID: 1       线程ID: 1
结束:8         任务ID: 5       线程ID: 6
结束:6         任务ID: 4       线程ID: 4
结束:1         任务ID: 6       线程ID: 8
结束:3         任务ID: 7       线程ID: 9
结束:5         任务ID: 8       线程ID: 10
循环是否完成运行:False
最小迭代索引:6
-----程序执行完毕-----

通过结果可以说明:

迭代虽然调用了Break()方法,但是其他迭代仍然可以同时运行。并且在中断前开始的所有任务都可以继续运行,直到结束。注意:Break()方法不会停止任何已经开始执行的迭代。

注:使用Break()方法时,常常需要结合LowestBreakIteration 和ShouldExitCurrentIteration 属性,关于具体用法,可以参见官方示例和说明。

Parallel.For()对线程初始化

Parallel.For()方法使用多个线程来执行循环,如果需要对每个线程进行初始化,可以使用Parallel.For()的另一个重载方法,该方法定义如下:

public static System.Threading.Tasks.ParallelLoopResult For (
    int fromInclusive, 
    int toExclusive, 
    Func localInit, 
    Func body, 
    Action localFinally);

除了前两个参数外,第三个委托参数Func,返回每个任务的本地数据的初始状态,这个方法仅用于执行迭代的每个线程调用一次。

Parallel.For()方法的第四个参数Func为循环体定义委托,该Func的第一个参数是迭代索引,第二个参数ParallelLoopState允许停止循环,第三个参数TLocal接收从Func localInit方法返回的值,最后一个参数为返回的类型,该类型和Func localInit中的TLocal要一致。

Parallel.For()方法的第五个参数Action为线程退出调用的方法,该方法接收一个TLocal类型的值,该值来自于第四个参数返回的结果。

通过Parallel.For()方法的定义可以看出,它的后三个参数都是基于同一个泛型类型(Tlocal)进行操作的,因此在实际使用时,这三个委托所传入的方法的参数和返回值的类型要保持一致。

示例代码如下:

public static void ParallelForWithInit()
{
    Parallel.For(0, 10, () =>
    {
        log("初始化线程");
        return "线程ID:" + Thread.CurrentThread.ManagedThreadId;
    },
    (i, pls, str1) =>
    {
        log($"迭代调用:{i} ,字符串:{str1}");
        Task.Delay(5000).Wait();
        return "i " + i;
    },
    (str1) =>
    {
        log("finally " + str1);
    });
}

在上述方法中,第一个委托的结果,将会作为参数传递给第二个委托的第三个参数,第二个委托的结果,又会作为参数传递给第三个委托。

该方法的执行结果如下:

初始化线程      任务ID: 2       线程ID: 3
初始化线程      任务ID: 3       线程ID: 4
初始化线程      任务ID: 4       线程ID: 5
迭代调用:6 ,字符串:线程ID:5         任务ID: 4       线程ID: 5
初始化线程      任务ID: 1       线程ID: 1
迭代调用:2 ,字符串:线程ID:3         任务ID: 2       线程ID: 3
迭代调用:4 ,字符串:线程ID:4         任务ID: 3       线程ID: 4
迭代调用:0 ,字符串:线程ID:1         任务ID: 1       线程ID: 1
初始化线程      任务ID: 5       线程ID: 6
迭代调用:8 ,字符串:线程ID:6         任务ID: 5       线程ID: 6
初始化线程      任务ID: 6       线程ID: 8
迭代调用:1 ,字符串:线程ID:8         任务ID: 6       线程ID: 8
初始化线程      任务ID: 7       线程ID: 9
迭代调用:3 ,字符串:线程ID:9         任务ID: 7       线程ID: 9
初始化线程      任务ID: 8       线程ID: 10
迭代调用:5 ,字符串:线程ID:10        任务ID: 8       线程ID: 10
初始化线程      任务ID: 9       线程ID: 11
迭代调用:7 ,字符串:线程ID:11        任务ID: 9       线程ID: 11
初始化线程      任务ID: 10      线程ID: 12
迭代调用:9 ,字符串:线程ID:12        任务ID: 10      线程ID: 12
finally i 8     任务ID: 5       线程ID: 6
finally i 0     任务ID: 1       线程ID: 1
finally i 2     任务ID: 2       线程ID: 3
finally i 4     任务ID: 3       线程ID: 4
finally i 6     任务ID: 4       线程ID: 5
finally i 1     任务ID: 6       线程ID: 8
finally i 3     任务ID: 7       线程ID: 9
finally i 5     任务ID: 8       线程ID: 10
finally i 7     任务ID: 9       线程ID: 11
finally i 9     任务ID: 10      线程ID: 12
-----程序执行完毕-----

Parallel.ForEach()

Parallel.ForEach()方法遍历实现了IEnumerable的集合,其方式类似于foreach语句,但以异步方式遍历。

public static void ParallelForEach()
{
    string[] data = { "zero", "one", "two", "three", "four",
        "five", "six", "seven", "eight", "nine",
        "ten", "eleven", "twelve" };

    ParallelLoopResult result = Parallel.ForEach(
        data, s => { Console.WriteLine(s); });

    //如果需要终端循环,可以使用重载版本和ParallelLoopState参数
    ParallelLoopResult result2 = Parallel.ForEach(
        data, (s, pls, l) => { Console.WriteLine(s); });
}

如果需要中断循环,可以使用ForEach()方法的重载版本和ParallelLoopState参数,其方式和For()方法相同。

使用Parallel.Invoke()同时调用多个方法

如果多个任务将并行运行,就可以使用Parallel.Invoke()方法,它提供了任务并行性模式。Parallel.Invoke()方法允许传递一个Action委托的数组,在其中可以指定将运行的方法。

public static void ParallelInvoke()
{
    Parallel.Invoke(Foo, Bar);
}
public static void Foo()
{
    Console.WriteLine("Foo");
}
public static void Bar()
{
    Console.WriteLine("Bar");
}

Parallel总结

一般来说,优先使用Parallel类进行并行处理,它既可以用于任务,也可以用于数据并行性。如果Parallel类不能很好的解决,或者需要细致的控制,或者不想等到Parallel类结束后再开始其他操作动作,就可以使用Task类。也可以结合使用Task类和Parallel类,具体根据情况而定。

WhenAny():只要有一个任务完成就返回调用任务。
  • WaitAny():阻塞调用任务,等待任何一个任务完成。
  • Delay():指定从这个方法返回的任务完成前要等待的毫秒数。
  • Yield():如果将释放CPU,从而允许其他任务运行,就可以调用Task.Yield()方法。该方法释放CPU,让其他任务运行。如果没有其他任务等待运行,调用Task.Yield()的任务就立即继续执行。否则,需要等到再次调度CPU,以调用任务。
  • .NET API——Task.Wait()方法
  • .NET API——Parallel.For()方法
  • .NET API——Parallel.ForEach()方法
  • 本文后续会随着知识的积累不断补充和更新,内容如有错误,欢迎指正。

    最后一次更新时间 :2018-07-30


    来自 “ ITPUB博客 ” ,链接:https://blog.itpub.net/1065/viewspace-2811280/ ,如需转载,请注明出处,否则将追究法律责任。

    请登录后发表评论 登录
    全部评论

    注册时间:2019-04-15

    • 博文量
      0
    • 访问量
      467