c# - 我是否需要使用默认的 TaskScheduler 来同步 Tasks 之间的资源访问?
问题描述
我正在使用任务和等待/异步进行编程。我假设多线程的工作方式就像它在 NodeJS 或 Python 中一样,也就是说,它没有,一切都在同一个线程上运行。但是我一直在尝试了解任务实际上是如何执行的,我的理解是它们是由 TaskScheduler.Default 执行的,它的实现是隐藏的,但可以预期使用 ThreadPool。
我是否应该像所有任务都可以在任何线程中运行一样进行编程?
我的异步编程范围是相当轻量级的 CPU 工作,由几个无限循环组成,这些循环确实有效,然后在 Task.Delay 上等待几秒钟。现在唯一的共享资源是一个 int,每次我写网络消息时都会增加,但将来我希望我的任务将是共享字典和列表。
我还有一个网络任务,它连接到 TCP 服务器并使用我在 BeginRead+EndRead 上实现的任务读取消息。Read 函数由一个无限循环调用,该循环读取一条消息,对其进行处理,然后读取一条新消息。
void OnRead(IAsyncResult result)
{
var pair = (Tuple<TaskCompletionSource<int>, NetworkStream>)result.AsyncState;
int count = pair.Item2.EndRead(result);
pair.Item1.SetResult(count);
}
async Task<byte[]> Read(NetworkStream stream, uint size)
{
var result = new byte[size];
var count = 0;
while(count < size)
{
var tcs = new TaskCompletionSource<int>();
stream.BeginRead(result, count, result.Length - (int)count, new AsyncCallback(OnRead), Tuple.Create(tcs, stream));
count += await tcs.Task;
}
return result;
}
我使用同步写入写入 NetworkStream。
解决方案
我假设多线程的工作方式就像它在 NodeJS 或 Python 中一样,也就是说,它没有,一切都在同一个线程上运行。但是我一直在尝试了解任务实际上是如何执行的,我的理解是它们是由 TaskScheduler.Default 执行的,它的实现是隐藏的,但可以预期使用 ThreadPool。
不完全是。
首先,Task
在 .NET 中可以是两个完全不同的东西。委托任务代表可以在某个线程上运行的代码,使用 aTaskScheduler
来确定它们运行的位置和方式。委托任务是在原始任务并行库中引入的,几乎从未与异步代码一起使用。另一种Task
是承诺任务。Promise
这些与JavaScript中的更相似:它们可以表示任何东西——它们只是一个“尚未完成”或“已完成结果”或“已完成错误”的对象。这是不同类型任务的不同状态图的对比。
因此,首先要认识到的是,就像您不会在 JavaScript 中“执行 Promise”一样,您也不会在 .NET 中“执行(Promise)任务”。所以询问它在哪个线程上运行是没有意义的,因为它们不会在任何地方运行。
但是,JS 和 C# 都有一个async
/await
语言结构,允许您编写更自然的代码来控制Promise。当async
方法完成时,promise 完成;如果该async
方法抛出,则 Promise 是错误的。
那么问题就变成了:控制这个承诺的代码在哪里运行?
在 JavaScript 世界中,答案是显而易见的:只有一个线程,所以这是代码运行的地方。在 .NET 世界中,答案有点复杂。我的异步介绍给出了核心概念:每个async
方法都开始在调用线程上同步执行,就像任何其他方法一样。当它由于 a 而产生时await
,它将捕获其“上下文”。然后,当该async
方法准备好在 之后恢复时await
,它会在该“上下文”中恢复。
“上下文”是SynchronizationContext.Current
,除非它是null
,在这种情况下上下文是TaskScheduler.Current
。在现代代码中,“上下文”通常是 GUI 线程上下文(始终在 GUI 线程上恢复)或线程池上下文(在任何可用的线程池线程上恢复)。
我是否应该像所有任务都可以在任何线程中运行一样进行编程?
async
如果在没有上下文的情况下调用方法中的代码,则可以在线程池线程上恢复。
是否需要在 Tasks 之间同步资源访问
可能不是。和关键字旨在允许轻松编写串行代码async
。await
因此,无需将 a 之前的代码与;await
之后的代码同步。await
之后的代码await
将始终在 之前的代码之后运行await
,即使它在不同的线程上运行。此外,await
注入所有必要的线程屏障,因此不存在乱序读取或类似问题。
但是,如果您的代码同时运行多个async
方法,并且这些方法共享数据,则需要同步。我有一篇博客文章涵盖了这种偶然的隐式并行性(在文章的末尾)。一般来说,异步代码鼓励返回结果而不是应用副作用,只要你这样做,隐式并行性就不成问题了。
推荐阅读
- python - 我想让python为我按住一个键
- javascript - 使用 Jest 和 React 测试库测试 `img.onLoad`/`img.onError`
- javascript - 谷歌表格中特定表格的时间戳
- python - 索引数组中重复条目的 Numpy 总和
- python - 尝试请求页面时读取超时
- node.js - 我需要帮助尝试通过节点服务器的身份验证调用 3rd 方 api
- python - Python 登录到需要 MFA 令牌的网站
- java - 如何按从小到大的顺序对带有整数和字母的字符串进行排序?(爪哇)
- c# - Mailkit asp.net 核心。如何使用密码,使其未显示在我的代码中
- c - 带有动态数组的 C 程序中的分段错误(核心转储)错误