【问题标题】:Threads not being created correctly. Very simple WaitAny c#未正确创建线程。非常简单的WaitAny c#
【发布时间】:2019-07-09 15:40:13
【问题描述】:

我有一些非常简单的代码试图对现有脚本进行多线程处理。

在 Visual Studio 中检查履带窗口并调用 Thread.CurrentThread.ManagedThreadId 时,它始终报告为与启动进程相同的线程。结束时,它会报告一个不同的线程 ID。

线程似乎确实在异步执行任务,但是 Visual Studio 的日志记录和输出让我不这么认为。

请有人澄清发生了什么,如果我在我的方法中犯了错误?

namespace ResolveGoogleURLs
{
    class Program
    {
        public static void Main(string[] args)
        {
            HomeController oHC = new HomeController();
        }
    }
}

using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace ResolveGoogleURLs
{
    class HomeController
    {
        public static int MaxJobs = 5;
        public static int RecordsPerJob = 1000;

        public static List<Task> TaskList = new List<Task>(MaxJobs);


        public HomeController()
        {
            CreateJobs();

            MonitorTasks();
        }


        public void MonitorTasks()
        {
            while (1 == 1)
            {
                Task.WaitAny(TaskList.ToArray());

                TaskList.RemoveAll(x => x.IsCompleted);

                Console.WriteLine("Task complete! Launching new...");
                CreateJobs();
            }
        }


        public async Task CreateJob()
        {
            Console.WriteLine("Thread {0} - Start", Thread.CurrentThread.ManagedThreadId);

            // read in results from sql

            await Task.Delay(10000);
            Console.WriteLine("Thread {0} - End", Thread.CurrentThread.ManagedThreadId);
        }


        public void CreateJobs()
        {
            while (TaskList.Count < MaxJobs)
            {
                TaskList.Add( CreateJob() );
            }
        }
    }
}

输出:

> Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 4 - End
Thread 5 - End
Thread 4 - End
Thread 6 - End
Thread 8 - End
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Thread 7 - End
Thread 6 - End
Thread 5 - End
Thread 4 - End
Thread 8 - End
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start
Thread 1 - Start
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start
Thread 10 - End
Thread 9 - End
Task complete! Launching new...
Thread 1 - Start
Thread 7 - End
Thread 4 - End
Thread 6 - End
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start
Task complete! Launching new...
Thread 1 - Start
Thread 1 - Start

【问题讨论】:

  • code that's attempting to multi-thread - 不,这是not 尝试这样做。 Same thread as starting the process. When ending it reports back a different thread id - 因为在控制台应用程序中,延续 does not have 在原始线程上运行。 seem to be performing the task asynchronously, but the logging and output from visual studio are making me think otherwise - 请参阅 stackoverflow.com/q/37419572/11683blog.stephencleary.com/2013/11/there-is-no-thread.html
  • 目前尚不清楚您获得的结果与您期望的结果有何不同。如果我没记错的话,这不是问题吗 - 为什么我会得到这个,不是那个?预期的结果是什么?我的期望是哪个线程启动任务以及哪个线程完成它会有点不可预测,这就是这个输出的样子。
  • 任务!=线程;这在我看来是完全正确和预期的; 希望它是什么样子,为什么?
  • 您正在处理任务。任务不等同于线程。系统将在其可用的任何可用线程上执行任务,简而言之。 awaited 操作之后的异步方法中的任何代码都可能在任意线程上下文中执行,甚至可能不是线程池中的后台线程,这完全取决于等待操作的行为/性质......
  • 执行异步与创建线程同义。主要是为了避免不必要的线程。

标签: c# multithreading async-await


【解决方案1】:

任务 (class Task) 与线程 (class Thread) 不同。

可以将线程想象成一个虚拟 CPU,它可以与其他线程同时运行其代码。每个线程都有自己的堆栈(存储局部变量和函数参数的地方)。在 .NET 中,线程被映射到本机线程 - 由平台(操作系统)支持,具有线程内核对象、内核模式堆栈、线程执行块等内容。这使得线程成为一个非常重量级的对象。创建新线程非常耗时,即使线程休眠(不执行其代码),它仍然会消耗大量内存(线程堆栈)。

操作系统会定期检查所有正在运行的线程,并根据它们的优先级为它们分配一个时隙,此时线程可以使用真正的 CPU(执行其代码)。

由于线程消耗内存,它们创建速度很慢,并且运行太多线程会损害整体系统性能,因此发明了任务(线程池)。

Task 在内部使用线程,因为线程是在 .NET 中并行运行代码的唯一方法。 (实际上这是运行任何代码的唯一方法。)但它以有效的方式执行。

当一个 .NET 进程启动时,它会在内部创建一个线程池 - 一个用于执行任务的线程池。

任务库是这样实现的,当一个进程启动时,一个线程池只包含一个线程。如果您开始创建新任务,它们将被存储到一个队列中,该单个线程从该队列中获取并执行一个又一个任务。但是 .NET 会监视该线程是否没有因太多任务而超载 - 任务在线程池队列中等待“太久”的情况。如果 - 基于其内部标准 - 它检测到最初创建的线程过载,它会创建一个新线程。所以线程池现在有 2 个线程,任务可以在其中 2 个线程上并行运行。这个过程可以重复,所以如果负载很重,线程池可以有 3、4 或更多线程。

另一方面,如果新任务的频率下降,并且线程池线程没有任务要执行,则在“一段时间”之后(“一段时间”是由内部线程池标准定义的)线程池可以决定释放一些线程——以节省系统资源。线程池线程数可以下拉到最初创建的单线程。

所以任务库内部使用线程,但它试图以有效的方式使用它们。线程数根据程序要执行的任务数来增加和减少。

在几乎所有情况下,任务都应该是使用“原始”线程的首选解决方案。

【讨论】:

  • @atoms 这个答案是错误的并且具有误导性。任务不是线程池,您可以通过访问问题下的 cmets 中提供的链接清楚地看到。
  • 任务不是线程池。任务是允许使用线程池的轻量级对象。
  • 这里引用了任务类的 MS 文档。由于 Task 对象执行的工作通常在线程池线程上异步执行,而不是在主应用程序线程上同步执行,因此您可以使用 Status 属性以及 IsCanceled、IsCompleted 和 IsFaulted 属性来确定任务的状态任务。
  • Because threads consume memory, they are slow to create and running too many threads hurts overall system performance, tasks (thread pools) were invented. - 这是错误的。 Task internally uses threads, because threads are the only way how to run code in parallel way in .NET - 这是更错误的。请阅读 cmets 中提供的问题链接,从 blog.stephencleary.com/2013/11/there-is-no-thread.html 开始。
  • 我读过那篇文章。但你从字面上理解。有线程。每个多任务操作系统都基于线程。作者想说什么——我完全同意他的观点,你应该总是使用任务来执行 IO 操作。但是由线程来执行任务代码。顺便说一句 - 允许设备驱动程序阻塞线程。驱动程序本身创建的非任意线程。有关更详细的解释,请参阅 Walter Oney 编写的优秀书籍 Programming Windows Drivers Model 的第 5 章。他在那里做了很好的解释 IO 操作是如何在内核级别实现的。
猜你喜欢
  • 2011-07-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-08-30
  • 2011-04-18
  • 1970-01-01
  • 2015-01-07
  • 2011-04-03
相关资源
最近更新 更多