代码之家  ›  专栏  ›  技术社区  ›  EJoshuaS - Stand with Ukraine

.NET框架如何分配线程ID?

  •  4
  • EJoshuaS - Stand with Ukraine  · 技术社区  · 6 年前

    我的问题与 this question 。这个问题中的代码在一个循环中生成了多个线程,那里的操作人员观察到日志中的线程ID似乎随着时间的推移而不断增加。这个问题是关于Java的,但它让我思考:JVM和.NETFramework如何首先分配线程ID?我对这一点特别感兴趣,因为OP在他的文章中描述了这样的情况(用于验证线程是否按预期实际创建和销毁),以及有关如何解释Visual Studio诊断工具日志的更多上下文。我也对我自己对框架如何工作的理解感兴趣。

    我在这里主要问的是.NET框架,因为它可能太宽泛了,不能同时问这两个问题(尽管我肯定很高兴听到有关JVM的详细信息)。但是,下面是我在Visual Studio诊断工具的事件选项卡中获取的日志示例:

    程序输出 :线程0x44C已退出,代码为0(0x0)。

    以下是一些按顺序排列的线程ID:

    0xcb0
    0x2c4c
    0x2c5c
    0x1b10
    0x1a60
    0x27b4
    0x2b80
    0x2e04
    

    这些似乎不是特别连续的。例如,如果没有从Visual Studio的“线程”窗口中获取更多上下文,那么日志本身并没有提供非常丰富的信息,因此我希望首先了解更多有关如何分配这些日志的信息,可以为这些事件提供更多的上下文。

    下面是我使用的代码示例:

    await jobs.AsyncForEach(async delegate (Job job)
        {
           // Do some stuff, some of which involves async/await HttpClient calls to a RESTful API
        }, GlobalSettings.maxDegreeOfParallelism);
    

    jobs 属于类型 List<Job> , GlobalSettings.maxDegreeOfParallelism 是一个 const int 指定最大并行度(由于我们供应商的API节流阀),以及 AsyncForEach 是上的扩展方法 IEnumerable<T> :

    public static async Task AsyncForEach<T>(this IEnumerable<T> enumerable, Func<T, Task> action, int degreeOfParallelism)
    {
        List<Task> tasks = new List<Task>();
    
        foreach (T item in enumerable)
        {
            if (tasks.Count >= degreeOfParallelism)
            {
                await Task.WhenAny(tasks);
    
                tasks = tasks.Where(t => !t.IsCompleted).ToList();
            }
    
            Task actionTask = action(item);
    
            tasks.Add(actionTask);
        }
    
        await Task.WhenAll(tasks);
    }
    

    这目前可以在三个环境中的一个环境中运行:WPF应用程序、控制台应用程序或单元测试。我在这里展示的日志来自一个单元测试运行,但是控制台应用程序日志看起来非常相似。

    我知道 async / await 在这种情况下,工作方式有些不同,而且没有明确的保证,如果没有同步上下文,异步代码将运行在哪个线程上;不过,就其价值而言,我没有用 new Thread , ThreadPool.QueueUserWorkItem Task.Run 在这段代码的任何地方。

    当我在谷歌上搜索这个时,我看到了关于托管线程ID之间的区别以及如何从线程获取ID的文档。然而,这些并不能真正回答一个问题,即.NET框架是如何首先提出这些问题的。

    我还很清楚Visual Studio线程窗口,它显示线程的ID、关联进程、托管ID、类别、名称和位置。这也不能确切地回答框架如何分配这些任务的问题。

    1 回复  |  直到 6 年前
        1
  •  4
  •   Aloraman    6 年前

    没有。

    有两个不同的实体: 本地的 由操作系统创建的线程,以及 管理 由clr创建的线程。线程ID来自操作系统,managedThreadID来自clr。两者都是计数器,在clr启动时,through操作系统拥有更大的线程对象池。每个操作系统的线程ID都是唯一的,每个进程的clr线程都是唯一的。

    托管线程本质上是一种数据结构,它存储在本机线程的TLS内存部分,CLR可以对其进行编辑,因此允许托管线程通过光纤API从一个本机线程切换到另一个本机线程,并在一个本机线程上承载多个托管线程。你可以用 Thread.BeginThreadAffinity 将托管线程粘贴到同一个本机线程。此外,桌面应用程序的主托管线程必须映射到同一个本机线程(操作系统向特定本机线程的消息循环发送重新绘制、键盘事件和多个其他事件的消息,CLR将其接收),因此这些应用程序中的主线程是必需的。IRED有单线程单元模型,这确保了它。