简体   繁体   English

我是否需要使用默认的 TaskScheduler 同步任务之间的资源访问?

[英]Do I need to synchronize resource access between Tasks with the default TaskScheduler?

I'm programming with Tasks and await/async.我正在使用 Tasks 和 await/async 进行编程。 I assumed that the multithreading works like it does in NodeJS or Python, that is, it doesn't, everything just runs on the same thread.我假设多线程的工作方式与 NodeJS 或 Python 中的一样,也就是说,它不是,一切都在同一线程上运行。 But I've been trying to learn how Tasks actually get executed and my understanding is that they're executed by TaskScheduler.Default who's implementation is hidden but can be expected to use a ThreadPool.但是我一直在尝试了解 Tasks 是如何实际执行的,我的理解是它们是由 TaskScheduler.Default 执行的,它的实现是隐藏的,但可以预期使用 ThreadPool。

Should I be programming as if all my Tasks can run in any thread?我应该像我的所有任务都可以在任何线程中运行一样进行编程吗?

The extent of my asynchronous programming is fairly lightweight CPU work consisting of several infinite loops that do work and then await on Task.Delay for several seconds.我的异步编程的范围是相当轻量级的 CPU 工作,由几个无限循环组成,这些循环工作然后在 Task.Delay 上等待几秒钟。 Right now the only shared resources is an int that increments every time I write a network message but in the future I expect my tasks will be sharing Dictionaries and Lists.现在唯一的共享资源是一个 int,每次我写网络消息时它都会增加,但将来我希望我的任务将是共享字典和列表。

I also have a network Task that connects to a TCP server and reads messages using a Task I implemented on BeginRead+EndRead.我还有一个网络任务,它连接到 TCP 服务器并使用我在 BeginRead+EndRead 上实现的任务读取消息。 The Read function is called by an infinite loop that reads a messages, processes it, then reads a new message. 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;
        }

I write to the NetworkStream using synchronous writes.我使用同步写入写入 NetworkStream。

I assumed that the multithreading works like it does in NodeJS or Python, that is, it doesn't, everything just runs on the same thread.我假设多线程的工作方式与 NodeJS 或 Python 中的一样,也就是说,它不是,一切都在同一线程上运行。 But I've been trying to learn how Tasks actually get executed and my understanding is that they're executed by TaskScheduler.Default who's implementation is hidden but can be expected to use a ThreadPool.但是我一直在尝试了解 Tasks 是如何实际执行的,我的理解是它们是由 TaskScheduler.Default 执行的,它的实现是隐藏的,但可以预期使用 ThreadPool。

Not exactly.不完全是。

First, Task in .NET can be two completely different things .首先,.NET 中的Task可以是两个完全不同的东西 Delegate Tasks represent code that can run on some thread, using a TaskScheduler to determine where and how they run.委托任务代表可以在某个线程上运行的代码,使用TaskScheduler来确定它们在何处以及如何运行。 Delegate Tasks were introduced with the original Task Parallel Library and are almost never used with asynchronous code.委托任务是在原始任务并行库中引入的,几乎从未与异步代码一起使用。 The other kind of Task is Promise Tasks.另一种Task是承诺任务。 These are much more similar to Promise in JavaScript: they can represent anything - they're just an object that is either "not finished yet" or "finished with a result" or "finished with an error".它们更类似于 JavaScript 中的Promise :它们可以表示任何东西——它们只是一个“尚未完成”或“以结果完成”或“以错误完成”的对象。 Here's a contrast of the different state diagrams for the different kinds of tasks. 这是不同类型任务的不同状态图的对比。

So, the first thing to recognize is that just like you don't "execute a Promise" in JavaScript, you don't "execute a (Promise) Task" in .NET.因此,首先要认识到的是,就像您不在 JavaScript 中“执行 Promise”一样,您在 .NET 中也不“执行(Promise)任务”。 So asking what thread it runs on doesn't make sense, since they don't run anywhere.所以询问它在哪个线程上运行是没有意义的,因为它们不在任何地方运行

However, both JS and C# have an async / await language construct that allows you to write more natural code to control promises.但是,JS 和 C# 都有async / await语言结构,允许您编写更自然的代码来控制承诺。 When the async method completes, the promise is completed;async方法完成时,promise 完成; if the async method throws, the promise is faulted.如果async方法抛出,则承诺失败。

So the question then becomes: where does the code run that controls this promise?那么问题就变成了:控制这个承诺的代码在哪里运行?

In the JavaScript world, the answer is obvious: there is only one thread, so that is where the code runs.在 JavaScript 世界中,答案是显而易见的:只有一个线程,所以代码运行在那里。 In the .NET world, the answer is a bit more complex.在 .NET 世界中,答案有点复杂。 My async intro gives the core concepts: every async method begins executing synchronously, on the calling thread, just like any other method.我的异步介绍给出了核心概念:每个async方法都开始在调用线程上同步执行,就像任何其他方法一样。 When it yields due to an await , it will capture its "context".当它由于awaitawait ,它将捕获其“上下文”。 Then, when that async method is ready to resume after the await , it resumes within that "context".然后,当该async方法准备好在await之后恢复时,它会在该“上下文”中恢复。

The "context" is SynchronizationContext.Current , unless it is null , in which case the context is TaskScheduler.Current . “上下文”是SynchronizationContext.Current ,除非它是null ,在这种情况下上下文是TaskScheduler.Current In modern code, the "context" is usually either a GUI thread context (which always resumes on the GUI thread), or the thread pool context (which resumes on any available thread pool thread).在现代代码中,“上下文”通常是 GUI 线程上下文(始终在 GUI 线程上恢复)或线程池上下文(在任何可用的线程池线程上恢复)。

Should I be programming as if all my Tasks can run in any thread?我应该像我的所有任务都可以在任何线程中运行一样进行编程吗?

The code in your async methods can resume on a thread pool thread if it's called without a context.如果在没有上下文的情况下调用async方法中的代码,则它可以在线程池线程上恢复。

Do I need to synchronize resource access between Tasks我需要在任务之间同步资源访问吗

Probably not.可能不是。 The async and await keywords are designed to allow easy writing of serial code. asyncawait关键字旨在允许轻松编写串行代码。 So there's no need to synchronize code before an await with code after an await ;所以没有必要将await之前的代码与await之后的代码同步; the code after the await will always run after the code before the await , even if it runs on a different thread. await之后的代码将始终await之前的代码之后运行,即使它运行在不同的线程上。 Also, await injects all necessary thread barriers, so there's no issues around out-of-order reads or anything like that.此外, await注入了所有必要的线程屏障,因此不存在乱序读取或类似问题。

However, if your code runs multiple async methods at the same time, and those methods share data, then that would need to be synchronized.但是,如果您的代码同时运行多个async方法,并且这些方法共享数据,则需要同步。 I have a blog post that covers this kind of accidental implicit parallelism (at the end of the post).我有一篇博客文章介绍了这种意外的隐式并行(在文章末尾)。 Generally speaking, asynchronous code encourages returning results rather than applying side effects , and as long as you do that, implicit parallelism is less of a problem.一般来说,异步代码鼓励返回结果而不是应用副作用,只要你这样做,隐式并行性就不是问题。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM