我有一个主要任务是产生线程来做一些工作。工作完成后,它将写入控制台。
我的问题是,稍后创建的某些线程将比先前创建的线程完成得更快。但是我需要以与创建线程完全相同的顺序完成对控制台的写入。
因此,如果一个线程已经完成了它的任务,而一些早期的线程没有,那么它必须等到那些早期的线程也完成。
public class DoRead
{
public DoRead()
{
}
private void StartReading()
{
int i = 1;
while (i < 10000)
{
Runner r = new Runner(i, "Work" + i.ToString());
r.StartThread();
i += 1;
}
}
}
internal class Runner : System.IDisposable
{
int _count;
string _work = "";
public Runner(int Count, string Work)
{
_count = Count;
_work = Work;
}
public void StartThread()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(runThreadInPool), this);
}
public static void runThreadInPool(object obj)
{
((Runner)obj).run();
}
public void run()
{
try
{
Random r = new Random();
int num = r.Next(1000, 2000);
DateTime end = DateTime.Now.AddMilliseconds(num);
while (end > DateTime.Now)
{
}
Console.WriteLine(_count.ToString() + " : Done!");
}
catch
{
}
finally
{
_work = null;
}
}
public void Dispose()
{
this._work = null;
}
}
答案 0 :(得分:1)
可能有一种比我使用的更简单的方法,(我习惯于.Net 4.0)。
using System;
using System.Collections.Generic;
using System.Text;
using System.Threading;
namespace ConsoleApplication5
{
class Program
{
public static readonly int numOfTasks = 100;
public static int numTasksLeft = numOfTasks;
public static readonly object TaskDecrementLock = new object();
static void Main(string[] args)
{
DoRead dr = new DoRead();
dr.StartReading();
int tmpNumTasks = numTasksLeft;
while ( tmpNumTasks > 0 )
{
Thread.Sleep(1000);
tmpNumTasks = numTasksLeft;
}
List<string> strings = new List<string>();
lock( DoRead.locker )
{
for (int i = 1; i <= Program.numOfTasks; i++)
{
strings.Add( DoRead.dicto[i] );
}
}
foreach (string s in strings)
{
Console.WriteLine(s);
}
Console.ReadLine();
}
public class DoRead
{
public static readonly object locker = new object();
public static Dictionary<int, string> dicto = new Dictionary<int, string>();
public DoRead()
{
}
public void StartReading()
{
int i = 1;
while (i <= Program.numOfTasks )
{
Runner r = new Runner(i, "Work" + i.ToString());
r.StartThread();
i += 1;
}
}
}
internal class Runner : System.IDisposable
{
int _count;
string _work = "";
public Runner(int Count, string Work)
{
_count = Count;
_work = Work;
}
public void StartThread()
{
ThreadPool.QueueUserWorkItem(new WaitCallback(runThreadInPool), this);
}
public static void runThreadInPool(object obj)
{
Runner theRunner = ((Runner)obj);
string theString = theRunner.run();
lock (DoRead.locker)
{
DoRead.dicto.Add( theRunner._count, theString);
}
lock (Program.TaskDecrementLock)
{
Program.numTasksLeft--;
}
}
public string run()
{
try
{
Random r = new Random();
int num = r.Next(1000, 2000);
Thread.Sleep(num);
string theString = _count.ToString() + " : Done!";
return theString;
}
catch
{
}
finally
{
_work = null;
}
return "";
}
public void Dispose()
{
this._work = null;
}
}
}
}
基本上,我将您要从每个任务打印的字符串存储到索引为任务#的字典中。 (我使用锁来安全地访问字典。)
接下来,为了让主程序等到所有后台线程完成,我使用了对NumTasksLeft变量的另一个锁定访问。
我在Runner的回调中添加了东西。
使用busy循环是不好的做法,因此我将其更改为Thread.Sleep(num)语句。
只需将numOfTasks更改为10000以匹配您的示例。
我按顺序将返回字符串拉出字典,然后将其打印到屏幕上。
我确信你可以重构这个以移动或以其他方式处理全局变量,但这可行。
另外,你可能已经注意到我没有在命令中使用锁
tmpNumTasks = numTasksLeft;
这是线程安全的,因为numTasksLeft是一个在32位计算机上以原子方式读取的int。
答案 1 :(得分:0)
我对C#了解不多,但多线程的整个想法是你有多个线程独立执行,你永远不知道哪个先前会完成(你不应该期望早期的线程更早结束) )。
一种解决方法是,在处理线程中写出完成消息,让处理线程在某处设置一个标志(可能是一个没有元素的列表=没有生成的线程),并且有一个单独的线程打印完成消息基于该列表中的标志,并报告直到前一个标志连续“完成”的位置。
老实说,我觉得打印完成这样的消息并不合理。我认为改变设计更好地拥有这种毫无意义的“特征”。
答案 2 :(得分:0)
通常情况下,这些要求会增加序号,就像您已经完成的那样。
通常,处理线程的输出通过包含所有无序结果对象的列表(或字典)的过滤器对象提供,“保留它们”直到所有结果具有较低的顺序 - 数字已经进入。再次,类似于你已经做过的事情。
什么是不必要的是任何类型的sleep()循环。工作线程本身可以操作过滤器对象(可以锁定),或者工作线程可以将生产者 - 消费者 - 将结果排队到操作无序过滤器的“输出线程”。
此方案适用于池化工作线程,即。那些没有持续创造/终止/破坏开销的人。