Warning: file_get_contents(/data/phpspider/zhask/data//catemap/2/.net/20.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
C# C/.net 3.5SP1中线程的基本体系结构和生命周期_C#_.net_Multithreading_Architecture - Fatal编程技术网

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

C# C/.net 3.5SP1中线程的基本体系结构和生命周期,c#,.net,multithreading,architecture,C#,.net,Multithreading,Architecture,我想写我的第一个真正的多线程C应用程序。虽然我以前使用过BackgroundWorker,对lockobject了解一两点,但我从未使用过Thread对象、Monitor.Enter等,我完全不知道从哪里开始设计架构 我的程序基本上是在后台运行的。它每5分钟检查一次web服务。如果web服务返回数据,它将使用该数据创建作业并将其传递到作业队列。然后JobQueue按顺序处理这些作业-如果添加了一个新作业,而它仍在处理一个作业,它将对作业进行排队。此外,还有一个Web服务器,允许远程访问该程序 在

我想写我的第一个真正的多线程C应用程序。虽然我以前使用过BackgroundWorker,对lockobject了解一两点,但我从未使用过Thread对象、Monitor.Enter等,我完全不知道从哪里开始设计架构

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

在我看来,我需要4个线程:

主线 5分钟计时器和WebService线程 工作队列 网络服务器 线程2-4应该在程序启动时创建,并在程序结束时结束,因此它们只运行一次


如前所述,我真的不知道该体系结构将如何工作。线程1会做什么?当MyProgram类被实例化时,它应该有一个队列作为属性吗?我将如何开始我的线程?在我看来,我需要将一个函数传递到线程中——这个函数应该放在哪里?如果我有一个类MyJobQueueThreadClass,它具有线程3的所有函数,那么它将如何访问MyProgram类上的对象?如果一个线程只是一个函数,我如何防止它提前结束?如前所述,线程2等待5分钟,然后执行一系列函数,并重新启动5分钟计时器线程。Sleep300?一次又一次地,直到我的程序结束,在MyProgram的Close/Exit/Destructor中调用Thread.AbortThread2?

您不必担心主程序线程的生命周期问题-这不在您的掌握之中

您可以在主线程上设置一个计时器对象System.Threading.timer,它每5分钟运行一次-将使用.NET线程池中的一个线程回调已运行事件处理程序

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

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

您也可以使用BackgroundWorker来实现这一点,但是如果您想学习多线程,那么第一个选项将使您对线程有更多的了解


这种模式非常常见,通常被称为生产者-消费者,你可以用谷歌搜索它。这里的主要复杂性在于同步对队列的访问,因为它是生产者线程和消费者线程之间共享的,您不希望它们互相攻击。

您不必担心主程序线程的生命周期问题,这是您无法控制的

您可以在主线程上设置一个计时器对象System.Threading.timer,它每5分钟运行一次-将使用.NET线程池中的一个线程回调已运行事件处理程序

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

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

您也可以使用BackgroundWorker来实现这一点,但是如果您想学习多线程,那么第一个选项将使您对线程有更多的了解

这种模式非常常见,通常被称为生产者-消费者,你可以用谷歌搜索它。这里的主要复杂性在于同步对队列的访问,因为它是生产者线程和消费者线程之间共享的,并且您不希望它们相互干扰
s脚趾。

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

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

    private static Queue<Job> jobQueue;
让我们添加一个从web服务检索作业并将其添加到队列的方法:

    private static void RetrieveJob(object unused) {
        Job job = ... // retrieve job from webservice
        EnqueueJob(job);
    }
以及在循环中处理队列中作业的方法:

    private static void ProcessJobs() {
        while (true) {
            Job job = DequeueJob();
            // process job
        }
    }
让我们运行这个程序:

    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,因为在检索作业之前,作业队列为空

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

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

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

    private static void ProcessJobs() {
        while (true) {
            Job job = DequeueJob();
            if (job == null) {
                break;
            }
            // process job
        }
        // when ProcessJobs returns, the thread ends
    }
然后停止计时器并将特殊作业添加到作业队列:

    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();
    }
我希望这能含蓄地回答你的所有问题:-

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

用于小任务 在小的、周期性的任务中使用 对长时间运行的任务使用 从多个线程访问数据结构时,需要锁定数据结构

在大多数情况下,lock语句就可以了 如果有多个线程从很少更改的数据结构中读取数据,请使用。 如果数据结构是不可变的,则不需要锁。 当多个线程相互依赖时,例如,一个线程等待另一个线程完成一项任务时,将发出使用信号

, 如果任务正在等待线程结束 不要使用线程。中止,线程。中断,线程。恢复,线程。睡眠,线程。暂停,监视器。脉冲,监视器。等待


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

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

    private static Queue<Job> jobQueue;
让我们添加一个从web服务检索作业并将其添加到队列的方法:

    private static void RetrieveJob(object unused) {
        Job job = ... // retrieve job from webservice
        EnqueueJob(job);
    }
以及在循环中处理队列中作业的方法:

    private static void ProcessJobs() {
        while (true) {
            Job job = DequeueJob();
            // process job
        }
    }
让我们运行这个程序:

    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,因为在检索作业之前,作业队列为空

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

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

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

    private static void ProcessJobs() {
        while (true) {
            Job job = DequeueJob();
            if (job == null) {
                break;
            }
            // process job
        }
        // when ProcessJobs returns, the thread ends
    }
然后停止计时器并将特殊作业添加到作业队列:

    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();
    }
我希望这能含蓄地回答你的所有问题:-

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

用于小任务 在小的、周期性的任务中使用 对长时间运行的任务使用 从多个线程访问数据结构时,需要锁定数据结构

在大多数情况下,lock语句就可以了 如果有多个线程从很少更改的数据结构中读取数据,请使用。 如果数据结构是不可变的,则不需要锁。 当多个线程相互依赖时,例如,一个线程等待另一个线程完成一项任务时,将发出使用信号

, 如果任务正在等待线程结束 不要使用线程。中止,线程。中断,线程。恢复,线程。睡眠,线程。暂停,监视器。脉冲,监视器。等待


很抱歉,有很多问题,但如前所述,我有点迷路了。我试着用谷歌搜索了一下,但总是有点难,因为我不知道一篇文章是否仍然适用于3.5 SP1或是为1.1编写的。我知道有一些变化,例如ReaderWriterLockSlim,但我无法将它们放在透视图中。另外,如果没有任何方法来发现错误信息,那么谷歌搜索总是像是在雷区中扮演盲人的角色……很抱歉有很多问题,但正如前面所说,我有点迷路了。我试着用谷歌搜索了一下,但总是有点难,因为我不知道一篇文章是否仍然适用于3.5 SP1或是为1.1编写的。我知道有一些变化,例如ReaderWriterLockSlim,但我无法将它们放在透视图中。而且,如果没有任何方法来发现错误信息,谷歌搜索总是像在雷区里玩盲人游戏一样…+1的线程。加入我已经看到很多人忘记了这一点。解释得很好。我会使用start和stop显式方法,而不是使用null信号。否则,很好的回答哇,非常感谢!现在通读这篇文章,但这看起来很有帮助!线程。我想中止有点苛刻吧?相当于从任务管理器中杀死一个Trask?我如何优雅地结束我的Web服务器线程,它通常不会侦听作业队列?它确实与应用程序的其他部分进行交互,但大部分情况下它只是运行
它是自己的东西吗?用C…+1.+1为线程编程。加入我看到很多人忘记了这一点。解释得很好。我会使用start和stop显式方法,而不是使用null信号。否则,很好的回答哇,非常感谢!现在通读这篇文章,但这看起来很有帮助!线程。我想中止有点苛刻吧?相当于从任务管理器中杀死一个Trask?我如何优雅地结束我的Web服务器线程,它通常不会侦听作业队列?它确实与应用程序的一些其他部分交互,但它主要只是运行,并用C…+1进行自己的东西编程。