C#/ .net 3.5SP1中线程的基本体系结构和生命周期

Mic*_*tum 1 .net c# architecture multithreading

我想编写我的第一个真正的MultiThreaded C#应用程序.虽然之前我使用过BackgroundWorker并且知道关于lock(对象)的一两件事,但我从未使用Thread对象,Monitor.Enter等等,而我完全迷失了从哪里开始设计架构.

基本上我的程序在后台运行.每隔5分钟,它会检查一个Web服务.如果Web服务返回数据,它将从此数据中创建作业并将其传递给JobQueue.JobQueue然后按顺序处理这些作业 - 如果添加新作业但仍在处理作业时,它将对作业进行排队.此外,还有一个Web服务器允许远程访问该程序.

我看到它的方式,我需要4个线程:

  1. 主线程
  2. "5分钟计时器"和WebService线程
  3. JobQueue
  4. Web服务器

程序启动时应创建线程2-4,程序结束时应该结束,因此它们只运行一次.

如上所述,我真的不知道架构将如何工作.线程1会做什么?当MyProgram类被实例化时,它是否应该具有Queue<Job>属性?我该如何开始我的线程?据我所知,我需要将一个函数传递给线程 - 该函数应该放在哪里?如果我有一个类"MyJobQueueThreadClass"具有线程3的所有函数,那么如何访问MyProgram类上的Object?如果一个线程只是一个函数,我该如何阻止它提前结束?如上所述,线程2等待5分钟,然后执行一系列功能,并一遍又一遍地重启5分钟计时器(Thread.Sleep(300)?),直到我的程序结束(调用Thread.Abort(Thread2)in MyProgram的关闭/退出/解析器?)

dtb*_*dtb 16

让我们一步一步地完成它:

1.

class Program {
Run Code Online (Sandbox Code Playgroud)

作业队列是一种数据结构:

    private static Queue<Job> jobQueue;
Run Code Online (Sandbox Code Playgroud)

如果多个线程访问此数据结构,则需要将其锁定:

    private static void EnqueueJob(Job job) {
        lock (jobQueue) {
            jobQueue.Enqueue(job);
        }
    }

    private static Job DequeueJob() {
        lock (jobQueue) {
            return jobQueue.Dequeue();
        }
    }
Run Code Online (Sandbox Code Playgroud)

让我们添加一个从Web服务检索作业并将其添加到队列的方法:

    private static void RetrieveJob(object unused) {
        Job job = ... // retrieve job from webservice
        EnqueueJob(job);
    }
Run Code Online (Sandbox Code Playgroud)

以及在循环中处理队列中的作业的方法:

    private static void ProcessJobs() {
        while (true) {
            Job job = DequeueJob();
            // process job
        }
    }
Run Code Online (Sandbox Code Playgroud)

我们来运行这个程序:

    private static void Main() {
        // run RetrieveJob every 5 minutes using a timer
        Timer timer = new Timer(RetrieveJob);
        timer.Change(TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(5));

        // run ProcessJobs in thread
        Thread thread = new Thread(ProcessJobs);
        thread.Start();

        // block main thread
        Console.ReadLine();
    }
}
Run Code Online (Sandbox Code Playgroud)

2.

如果您运行该程序,您会注意到每5分钟添加一个作业.但是jobQueue.Dequeue()会抛出InvalidOperationException,因为在检索作业之前作业队列是空的.

为了解决这个问题,我们使用信号量将作业队列转换为阻塞队列:

    private static Semaphore semaphore = new Semaphore(0, int.MaxValue);

    private static void EnqueueJob(Job job) {
        lock (jobQueue) {
            jobQueue.Enqueue(job);
        }
        // signal availability of job
        semaphore.Release(1);
    }

    private static Job DequeueJob() {
        // wait until job is available
        semaphore.WaitOne();
        lock (jobQueue) {
            return jobQueue.Dequeue();
        }
    }
Run Code Online (Sandbox Code Playgroud)

3.

如果你再次运行程序,它不会抛出异常,一切都应该正常.但是你会注意到你必须杀死进程因为ProcessJobs-thread永远不会结束.那么,你如何结束你的计划?

我建议您定义一个特殊作业,指示作业处理的结束:

    private static void ProcessJobs() {
        while (true) {
            Job job = DequeueJob();
            if (job == null) {
                break;
            }
            // process job
        }
        // when ProcessJobs returns, the thread ends
    }
Run Code Online (Sandbox Code Playgroud)

然后停止计时器并将特殊作业添加到作业队列:

    private static void Main() {
        // run RetrieveJob every 5 minutes using a timer
        Timer timer = new Timer(RetrieveJob);
        timer.Change(TimeSpan.FromMinutes(0), TimeSpan.FromMinutes(5));

        // run ProcessJobs in thread
        Thread thread = new Thread(ProcessJobs);
        thread.Start();

        // block main thread
        Console.ReadLine();

        // stop the timer
        timer.Change(Timeout.Infinite, Timeout.Infinite);

        // add 'null' job and wait until ProcessJobs has finished
        EnqueueJob(null);
        thread.Join();
    }
Run Code Online (Sandbox Code Playgroud)

我希望这隐含地回答你所有的问题:-)

经验法则

  • 通过指定可访问所有必需数据结构的方法来启动线程

  • 从多个线程访问数据结构时,需要锁定数据结构

    • 在大多数情况下,lock声明都可以
    • 如果有很多线程从不经常更改的数据结构中读取,请使用ReaderWriterLockSlim.
    • 如果数据结构是不可变的,则不需要锁定.
  • 当多个线程相互依赖时(例如,等待另一个线程完成任务的线程)使用信号

  • 不要使用Thread.Abort,Thread.Interrupt,Thread.Resume,Thread.Sleep,Thread.Suspend,Monitor.Pulse,Monitor.Wait

  • **线程+1.Join()**我见过很多人忘记了这一点.好解释. (2认同)