EJo*_*ica 5 .net c# multithreading visual-studio async-await
我的问题与这个问题相关。该问题中的代码在循环内生成了多个线程,并且那里的 OP 观察到日志记录中的线程 ID 似乎随着时间的推移不断增加。这个问题是关于 Java 的,但它让我思考:JVM 和 .NET Framework 首先如何分配线程 ID?我对此特别感兴趣,例如 OP 在他的帖子中描述的情况(用于验证线程实际上是否按预期创建和销毁)以及有关如何解释 Visual Studio 诊断工具日志的更多上下文。我也对我自己对该框架如何工作的理解感兴趣。
我在这里主要询问有关 .NET Framework 的问题,因为它可能太宽泛,无法同时询问这两个问题(尽管我也很高兴听到有关 JVM 的详细信息)。但是,这是我在 Visual Studio 诊断工具的“事件”选项卡中获得的日志示例:
程序输出:线程 0x44c 已退出,代码为 0 (0x0)。
以下是按顺序排列的一些线程 ID:
0xcb0
0x2c4c
0x2c5c
0x1b10
0x1a60
0x27b4
0x2b80
0x2e04
Run Code Online (Sandbox Code Playgroud)
这些似乎并不是特别连续。该日志本身并没有提供太多信息,例如,如果没有从 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);
Run Code Online (Sandbox Code Playgroud)
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);
}
Run Code Online (Sandbox Code Playgroud)
目前它可以在以下三种环境之一中运行:WPF 应用程序、控制台应用程序或单元测试。我在这里显示的日志来自单元测试运行,但控制台应用程序日志看起来非常相似。
我确实意识到async/await在这种情况下的工作方式有所不同,并且如果没有同步上下文,则无法明确保证异步代码将在哪个线程上运行;不过,无论如何,我没有在该代码中的任何点使用new Thread、ThreadPool.QueueUserWorkItem或Task.Run显式地将任何代码分配给其自己的线程。
当我在 Google 上搜索此内容时,我看到了有关托管线程 ID 之间的差异以及如何从线程获取 ID 的文档。然而,这些并没有真正回答 .NET Framework 最初是如何提出这些问题的。
我也很清楚 Visual Studio 线程窗口,它显示了线程的 ID、关联进程、托管 ID、类别、名称和位置。这也没有完全回答框架如何分配这些的问题。
事实并非如此。
有两种不同的实体:由操作系统创建的本机线程和由 CLR 创建的托管线程。线程ID来自操作系统,ManagedThreadID来自CLR。两者都是计数器,通过操作系统在 CLR 启动时拥有更大的线程对象池。线程 ID 对于整个操作系统是唯一的,CLR 线程仅对于每个进程是唯一的。
托管线程本质上是一种数据结构,存储在本机线程的 TLS 内存部分中,CLR 可以对其进行编辑,因此允许托管线程从一个本机线程切换到另一个本机线程,并通过 Fiber API 在一个本机线程之上托管多个托管线程。您可以使用Thread.BeginThreadAffinity将托管线程粘贴到同一个本机线程。此外,桌面应用程序必须将其主托管线程映射到同一个本机线程(操作系统将重绘、键盘事件和多个其他事件的消息发送到特定本机线程的消息循环,CLR 接收它),因此主线程在这些应用程序中需要具有单线程公寓模型,这可以确保这一点。
| 归档时间: |
|
| 查看次数: |
272 次 |
| 最近记录: |