C# 中的多线程(Parallel.For(),线程类或异步和等待)
本文关键字:异步 等待 线程 多线程 Parallel For | 更新日期: 2023-09-27 18:31:02
简介
只是出于好奇,我怎样才能制作一个多线程循环,其中订单从低到高保证,并且在循环完成后执行了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);
}
此代码的以下结果是,现在不能保证从低到高的顺序,并且字符串Done
是在 for 循环结束之后编写的,但并不总是保证,但是我在 Console.WriteLine("Done");
之后放置了thread.Start();
。
1
2
3
5
7
7
8
9
9
Done
就像我第二次跑步一样:
2
3
3
4
5
6
7
8
Done
10
<小时 />案例树:async
和await
我也会尝试这种方式,但我不知道如何实现这种循环线程方式。
<小时 />问题
在所有情况下,我都没有我需要的结果。在第一种情况下,它不能保证被订购,而在第二种情况下,Done
循环运行之前写入。所以我的问题是我如何运行一个 for 循环,其中订单始终得到保证并在循环运行后Done
写入?
你问的是如何创建一个多线程循环,保证执行顺序。 如前所述,你不能。 但是,如果您想订购输出,则可以这样做。在这里你有两个选择
-
保存结果,直到它们全部完成(请参阅下面的伪代码):
- 创建输出输入字典
- 创建一个多线程循环来处理所有输入并记录输出
- 完成所有操作后,将输出(按输入顺序)写入屏幕
对 完成的输出进行排序。 这比#1更困难,但可以让您更快地显示结果。
- 创建一个多线程循环来处理所有输入
- 每个线程完成后,显示其输出,但不要只是将其写入屏幕,而是将其插入所有排序输出中的适当位置。
尽管您不应该将线程用于此类顺序用途。你所要求的是可能的。
这是我想要实现的目标的例子。它使用多个线程对象并保证顺序。但是,按顺序执行可能会获得更好的性能。
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();
}
所以要点是创建一堆线程,然后按顺序启动它们(实际上没有办法绕过那部分),然后直接加入主线程。将发生的情况是主线程将启动一个"子线程",然后等待它完成,一旦完成,它将启动下一个线程,并重复直到它不在列表中的线程中。
但是,如果您试图并行打印数字并保证输出的顺序,那么您就不走运了。仍然有可能,但是尝试维护顺序将获得的开销将破坏并行化的目的。
这是代码的真正并行版本:
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 数组来保持当前正在运行的所有线程的状态。
这基本上意味着在任何特定的"打印"线程中,您可以检查它们是否准备好在线程内打印,如果没有,您可以告诉它们循环睡眠,以便它们定期检查其状态。