首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >C#/.net 3.5SP1中线程的基本架构和生命周期

C#/.net 3.5SP1中线程的基本架构和生命周期
EN

Stack Overflow用户
提问于 2010-01-08 05:22:31
回答 2查看 3.4K关注 0票数 1

我想编写我的第一个真正的MultiThreaded C#应用程序。虽然我以前使用过BackgroundWorker,并且知道关于锁(对象)的一两件事,但我从来没有使用过线程对象、Monitor.Enter等等,我完全不知道从哪里开始设计体系结构。

基本上我的程序是在后台运行的。它每5分钟检查一次web服务。如果web服务返回数据,它将利用这些数据创建乔布斯并将其传递给JobQueue。然后,JobQueue依次对这些作业工作--如果在仍在处理一个作业时添加了一个新作业,它将对作业排队。此外,还有一个允许远程访问程序的Web服务器。

在我看来,我需要四根线:

  1. 主螺纹
  2. “5分钟计时器”和WebService线程
  3. The JobQueue
  4. Web服务器

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

如前所述,我真的不知道这个体系结构会如何运作。线程1会做什么?当MyProgram类被实例化时,它是否应该有一个Queue<Job>作为属性?我该怎么开始我的丝线?据我所见,我需要把一个函数传递到线程中--这个函数应该放在哪里?如果我有一个"MyJobQueueThreadClass“类,它拥有线程3的所有函数,那么如何访问MyProgram类上的对象?如果线程只是一个函数,我如何防止它提前结束呢?如前所述,线程2等待5分钟,然后执行一系列函数,并重新启动5分钟计时器(Thread.Sleep(300)?)一次又一次,直到我的程序结束(在关闭/退出/析构函数中调用Thread.Abort(Thread2)?)

EN

回答 2

Stack Overflow用户

回答已采纳

发布于 2010-01-08 06:01:23

让我们一步一步地看一遍:

1.

代码语言:javascript
复制
class Program {

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

代码语言:javascript
复制
    private static Queue<Job> jobQueue;

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

代码语言:javascript
复制
    private static void EnqueueJob(Job job) {
        lock (jobQueue) {
            jobQueue.Enqueue(job);
        }
    }

    private static Job DequeueJob() {
        lock (jobQueue) {
            return jobQueue.Dequeue();
        }
    }

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

代码语言:javascript
复制
    private static void RetrieveJob(object unused) {
        Job job = ... // retrieve job from webservice
        EnqueueJob(job);
    }

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

代码语言:javascript
复制
    private static void ProcessJobs() {
        while (true) {
            Job job = DequeueJob();
            // process job
        }
    }

让我们运行这个程序:

代码语言:javascript
复制
    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();
    }
}

2.

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

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

代码语言:javascript
复制
    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();
        }
    }

3.

如果您再次运行该程序,它将不会抛出异常,而且一切都应该正常工作。但是您会注意到,您必须终止该进程,因为ProcessJobs线程永远不会结束。那你怎么结束你的节目?

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

代码语言:javascript
复制
    private static void ProcessJobs() {
        while (true) {
            Job job = DequeueJob();
            if (job == null) {
                break;
            }
            // process job
        }
        // when ProcessJobs returns, the thread ends
    }

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

代码语言:javascript
复制
    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();
    }

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

经验法则

  • 通过指定可以访问所有必要数据结构的方法启动线程。
代码语言:javascript
复制
- Use [ThreadPool.QueueUserWorkItem](http://msdn.microsoft.com/en-us/library/system.threading.threadpool.queueuserworkitem.aspx) for small tasks
- Use a [Timer](http://msdn.microsoft.com/en-us/library/system.threading.timer.aspx) for small, periodic tasks
- Use a [Thread](http://msdn.microsoft.com/en-us/library/system.threading.thread.aspx) for long-running tasks

  • 从多个线程访问数据结构时,需要锁定数据结构。
代码语言:javascript
复制
- In most cases the `lock` statement will do
- Use a [ReaderWriterLockSlim](http://msdn.microsoft.com/en-us/library/system.threading.readerwriterlockslim.aspx) if there are many threads reading from a data structure that is infrequently changed.
- You don't need a lock if the data structure is immutable.

  • 当多个线程相互依赖时(例如,等待另一个线程完成任务的线程)使用信号
代码语言:javascript
复制
- [ManualResetEvent](http://msdn.microsoft.com/en-us/library/system.threading.manualresetevent.aspx), [AutoResetEvent](http://msdn.microsoft.com/en-us/library/system.threading.autoresetevent.aspx), [Semaphore](http://msdn.microsoft.com/en-us/library/system.threading.semaphore.aspx)
- [Thread.Join](http://msdn.microsoft.com/en-us/library/system.threading.thread.join.aspx) if the task is waiting for the thread to end

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

Stack Overflow用户

发布于 2010-01-08 05:54:33

在生命周期方面,您不必担心主程序线程--它已经超出了您的控制范围。

您可以在主线程上设置一个timer对象(System.Threading.Timer),它每隔5分钟就会过去一次--来自.NET线程池的一个线程将被用来调用您经过的事件处理程序。

我会使用这个线程连接到web服务,下载作业数据,并将作业推入作业队列,因为它是一个工作单元。一旦您完成了线程的工作,.NET将自动将其放回池中。定时器将继续发送重复此过程的经过事件。到目前为止,您实际上还不需要执行任何显式线程处理,这通常是一件好事!

然后,您需要一个从队列中弹出作业并对其进行处理的线程--您可以将其实现为一个使用工作线程模式封装Thread实例的类。它的功能是从作业队列中弹出作业并对其进行处理,并在工作完成后进入睡眠一段时间(队列为空)。当线程醒来时,它将继续循环,或者直到主线程发出停止的信号。

您也可以使用BackgroundWorker来完成这个任务,但是如果您想学习多线程,那么第一个选项将让您更深入地了解Thread

这种模式是相当普遍的,通常被称为生产者-消费者,你肯定可以谷歌的例子。这里的主要复杂性在于同步访问队列,因为它是生产者线程和使用者线程之间共享的,您不希望它们踩到对方的脚尖。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/2025699

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档