1

我有一个主要任务是产生线程来做一些工作。工作完成后,它将写入控制台。

我的问题是稍后创建的某些线程将比之前创建的线程更快地完成。但是,我需要以与创建线程相同的顺序完成对控制台的写入。

因此,如果一个线程已经完成了它的任务,而一些较早的线程还没有,它也必须等到那些较早的线程完成。

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

    }
4

3 回答 3

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 的回调中添加了一些东西。

使用繁忙循环是不好的做法,因此我将其更改为 Thread.Sleep(num) 语句。

只需将 numOfTasks 更改为 10000 以匹配您的示例。

我按顺序从字典中拉出返回字符串,然后将其打印到屏幕上。

我相信您可以重构它以移动或以其他方式处理全局变量,但这有效。

另外,你可能已经注意到我没有在命令中使用 lock

 tmpNumTasks = numTasksLeft;

那是线程安全的,因为 numTasksLeft 是一个 int,它可以在 32 位或更高的计算机上以原子方式读取。

于 2012-07-27T03:59:16.097 回答
0

我对 C# 了解不多,但多线程的整个想法是您有多个线程独立执行,您永远无法知道哪个线程会更早完成(并且您不应该期望更早的线程更早结束)。

一种解决方法是,代替在处理线程中写出完成消息,让处理线程在某处设置一个标志(可能是一个没有元素的列表=没有产生线程),并让一个单独的线程根据该列表中的标志,并报告上一个标志连续“完成”的位置。

老实说,无论如何,我觉得你打印这样的完成消息是不合理的。我认为改变设计更好地拥有这种毫无意义的“功能”。

于 2012-07-27T03:24:33.257 回答
0

通常,这些要求通过递增的序列号来满足,就像您已经完成的那样。

通常,处理线程的输出通过过滤器对象提供,该过滤器对象包含所有无序结果对象的列表(或字典),“阻止它们”直到所有具有较低序列号的结果都出现再次,类似于您已经完成的操作。

不需要的是任何类型的 sleep() 循环。工作线程本身可以操作过滤器对象(这将是一个锁),或者工作线程可以将结果生产者-消费者-排队到操作无序过滤器的“输出线程”。

该方案适用于池化工作线程,即。那些没有持续创建/终止/销毁开销的人。

于 2012-07-27T09:41:41.497 回答