C#中的多线程(Parallel.For(),Threading类或async和await)

时间:2016-01-18 17:26:45

标签: c# multithreading

简介

出于好奇,我怎样才能建立一个多线程循环,从而保证从低到高的顺序,并且在循环完成后执行了Console.WriteLine("Done");。我已经尝试了下面解释的三个案例。

案例一:Parallel.For(...)

如果要创建一个在C#中并行运行的for循环,则可以使用以下代码:

ParallelLoopResult result = Parallel.For(0, 10, i => 
{
    Console.WriteLine($"{i}, task: {Task.CurrentId}, Thread: {Thread.CurrentThread.ManagedThreadId}";
    Thread.Sleep(10);
}

Console.WriteLine($"Is completed: {result.IsCompleted}";

这将提供订单无法保证的下一个输出。如果再次运行代码,您将看到不同的结果。程序的运行顺序为0-2-4-6-8 -...,包含五个任务和六个线程。

0, task: 1, thread: 1
2, task: 2, thread: 3
4, task: 3, thread: 4
6, task: 4, thread: 5
8, task: 5, thread: 6
5, task: 3, thread: 4
7, task: 4, thread: 5
9, task: 5, thread: 6
3, task: 2, thread: 3
1, task: 1, thread: 1
Is completed: True

案例二:Thread

我还尝试使用线程而不是Parallel类。这是代码:

public static void Main(string[] args)
{
    for (int i = 0; i < number; i++)
    {
        Thread tr = new Thread(() => Print(i));
        tr.IsBackground = true;
        tr.Start();
    }

    Console.WriteLine("Done");
}

private static void Print(int i)
{
    Console.WriteLine(i);
    Thread.Sleep(10);
}

现在这个代码的结果是,从低到高的顺序也没有保证顺序,并且在for循环结束之后写了字符串Done但是并不总是保证,但是我&#39;在thread.Start();之后放置Console.WriteLine("Done");

1
2
3
5
7
7
8
9
9
Done

就像我的第二次跑步一样:

2
3
3
4
5
6
7
8
Done
10

案例树:asyncawait

我也会尝试这种方式,但我不知道如何为循环实现这种线程方式。

问题

在所有情况下,我都没有得到我需要的结果。在第一种情况下,它不能保证被排序,在第二种情况下,在循环运行之前已经写入Done。所以我的问题是如何运行for循环,在循环运行后始终保证顺序并写入Done

2 个答案:

答案 0 :(得分:2)

您正在询问如何创建一个保证执行顺序的多线程循环。如前所述,你不能。但是,如果您想订购输出,可以这样做。这里有两个选项

  1. 保存结果直到它们全部完成(参见下面的伪代码):

    • 创建输出输入词典
    • 创建一个多线程循环来处理所有输入并记录输出
    • 一旦完成,将输出(按输入顺序)写入屏幕
  2. 在输出完成时订购输出。这比#1更难,但允许您更快地显示结果。

    • 创建一个多线程循环来处理所有输入
    • 每个线程完成后,显示其输出,但不要将其写入屏幕,将其插入所有已排序输出中的适当位置。

答案 1 :(得分:0)

虽然您不应该使用Threads进行此类顺序使用。你可以要求的是什么。

以下是您尝试实现目标的示例。它使用多个线程对象并保证顺序。但是,按顺序执行此操作可能会获得更好的性能。

    public static void PrintDone()
    {
        Console.WriteLine("Done");
    }

    public static void Print(int i)
    {
        Console.WriteLine(i);
        Thread.Sleep(10);
    }

    static void Main(string[] args)
    {

        List<Thread> threads = new List<Thread>();
        for(int i = 0; i < 10; ++i)
        {
            int numCopy = i;
            Thread th = new Thread(() => Print(numCopy));
            threads.Add(th);
        }

        for (int i = 0; i < 10; ++i)
        {
            threads[i].Start();
            threads[i].Join();
        }

        PrintDone();
        Console.ReadLine();

    }

因此,要点是创建一堆线程,然后按顺序启动它们(实际上并没有绕过那部分),然后直接加入主线程。将要发生的是主线程将启动&#34; subthread&#34;然后等待它完成,一旦它完成它将开始下一个线程,并重复直到它超出列表中的线程。

但是,如果您尝试并行化数字的打印并保证输出的顺序,那么您运气不好。仍然可能,但是你试图维持订单所带来的开销将会破坏并行化的目的。

这是代码的真正并行版本:

    public static void PrintDone()
    {
        Console.WriteLine("Done");
    }

    public static void Print(int i, int[] threadStatus)
    {
        if(i != 0)
        {
            while(threadStatus[i-1] < 0)
            {
                Thread.Sleep(10);
            }
        }
        Thread.Sleep(100);
        threadStatus[i] = i;
        Console.WriteLine(i);
    }

    static void Main(string[] args)
    {
        int[] threadStatus = Enumerable.Repeat(-1, 10).ToArray();
        List<Thread> threads = new List<Thread>();
        for(int i = 0; i < 10; ++i)
        {
            int numCopy = i;
            Thread th = new Thread(() => Print(numCopy, threadStatus));
            threads.Add(th);
        }

        for (int i = 0; i < 10; ++i)
        {
            threads[i].Start();
        }

        threads[9].Join();
        PrintDone();
        Console.ReadLine();

    }

由于每个线程只能写入int数组的一部分并且读取是原子的,因此我们可以使用int数组来保持当前正在运行的所有线程的状态。

这基本上意味着在任何特定的&#34; print&#34;线程你可以检查它们是否已准备好在线程内打印,如果没有,你告诉它们在循环中睡觉,以便定期检查它们的状态。