Concurrency in C# Cookbook中文翻译 :1.1.并发性概述:异步编程简介

Chapter 1. Concurrency: An Overview 第1章。并发性:概述

Concurrency is a key aspect of beautiful software. For decades, concurrency was possible but difficult to achieve. Concurrent software was difficult to write,difficult to debug, and difficult to maintain. As a result, many developers chose the easier path and avoided concurrency. With the libraries and language features available for modern .NET programs, concurrency is now much easier.

并发性是漂亮软件的一个关键方面。几十年来,并行是可能的,但很难实现。并发软件很难编写,很难调试,也很难维护。因此,许多开发人员选择了更简单的路径并避免了并发性。有了现代.NET程序可用的库和语言特性,并发性现在变得容易多了。

Microsoft has led the way in significantly lowering the bar for concurrency.Previously, concurrent programming was the domain of experts; these days,every developer can (and should) embrace concurrency.

微软在显著降低并发性门槛方面走在了前面。以前,并发编程是专家的领域;现在,每个开发人员都可以(而且应该)拥抱并发性。

1 Introduction to Concurrency 介绍并发

Before continuing, I’d like to clear up some terminology that I’ll be using throughout this book. These are my own definitions that I use consistently to disambiguate different programming techniques. Let’s start with concurrency.

在继续之前,我想澄清一些我将在本书中使用的术语。这些是我自己的定义,我经常使用它们来消除不同编程技术的歧义。让我们从并发性开始。

1.1 Concurrency:Doing more than one thing at a time.

并发性:一次做不止一件事。

I hope it’s obvious how concurrency is helpful. End-user applications use concurrency to respond to user input while writing to a database. Server applications use concurrency to respond to a second request while finishing the first request. You need concurrency any time you need an application to do one thing while it’s working on something else. Almost every software application in the world can benefit from concurrency.Most developers hearing the term “concurrency” immediately think of “multithreading.” I’d like to draw a distinction between these two.

我希望并发性的帮助显而易见。终端用户应用程序在写入数据库时使用并发性响应用户输入。服务器应用程序在完成第一个请求时使用并发性响应第二个请求。当你需要应用程序在做一件事的同时又在做另一件事的时候,你就需要并发性。世界上几乎所有的软件应用程序都可以从并发中受益。大多数开发人员一听到“并发”这个词,马上就会想到“多线程”。“我想区分一下这两者。

1.2 Multithreading:A form of concurrency that uses multiple threads of execution.

多线程:一种使用多个执行线程的并发形式。

Multithreading refers to literally using multiple threads. As demonstrated in many recipes in this book, multithreading is one form of concurrency, but certainly not the only one. In fact, direct use of low-level threading types has almost no purpose in a modern application; higher-level abstractions are more powerful and more efficient than old-school multithreading. For that reason, I’ll minimize my coverage of outdated techniques. None of the multithreading recipes in this book use the Thread or BackgroundWorker types; they have been replaced with superior alternatives.

多线程字面上是指使用多个线程。正如本书中许多食谱所展示的那样,多线程是并发的一种形式,但肯定不是唯一的一种。事实上,在现代应用程序中,直接使用低级线程类型几乎没有任何意义;高级抽象比传统的多线程更强大、更高效。出于这个原因,我将尽量减少对过时技术的报道。本书中没有一个多线程配方使用Thread或BackgroundWorker类型;它们已被更好的替代品所取代。

WARNING警告

As soon as you type new Thread(), it’s over; your project already has legacy code.

只要你输入new Thread(),它就结束了;您的项目已经有遗留代码。

But don’t get the idea that multithreading is dead! Multithreading lives on in the thread pool, a useful place to queue work that automatically adjusts itself according to demand. In turn, the thread pool enables another important form of concurrency: parallel processing.

但是不要认为多线程已经死了!多线程存在于线程池中,线程池是根据需要自动调整自身的队列工作的有用位置。反过来,线程池支持另一种重要的并发形式:并行处理。

1.3 Parallel processing:Doing lots of work by dividing it up among multiple threads that run concurrently.

并行处理:通过在多个并发运行的线程之间进行大量的工作。

Parallel processing (or parallel programming) uses multithreading to maximize the use of multiple processor cores. Modern CPUs have multiple cores, and if there’s a lot of work to do, then it makes no sense to make one core do all the work while the others sit idle. Parallel processing splits the work among multiple threads, which can each run independently on a different core.

并行处理(或并行编程)使用多线程来最大限度地使用多个处理器核。现代的cpu有多个核,如果有很多工作要做,那么让一个核做所有的工作而其他核闲置是没有意义的。并行处理在多个线程之间分割工作,每个线程可以在不同的核上独立运行。

Parallel processing is one type of multithreading, and multithreading is one type of concurrency. There’s another type of concurrency that is important in modern applications but isn’t as familiar to many developers: asynchronous programming.

并行处理是多线程的一种,多线程是并发的一种。还有另一种类型的并发在现代应用程序中很重要,但许多开发人员并不熟悉:异步编程。

1.4 Asynchronous programming:A form of concurrency that uses futures or callbacks to avoid unnecessary threads.

异步编程:一种使用future或回调来避免不必要线程的并发形式。

A future (or promise) is a type that represents some operation that will complete in the future. Some modern future types in .NET are Task and Task. Older asynchronous APIs use callbacks or events instead of futures. Asynchronous programming is centered around the idea of an asynchronous operation: some operation that is started that will complete some time later. While the operation is in progress, it doesn’t block the original thread; the thread that starts the operation is free to do other work. When the operation completes, it notifies its future or invokes its callback or event to let the application know the operation is finished.

future(或promise)是一种表示将在将来完成的操作的类型。在.NET中一些现代的新的类型是Task和Task。旧的异步api使用回调或事件而不是future。异步编程的核心思想是异步操作:一些已启动的操作将在一段时间后完成。当操作正在进行时,它不会阻塞原始线程;启动该操作的线程可以自由地做其他工作。当操作完成时,它通知它的future或调用它的回调或事件,让应用程序知道操作已经完成。

Asynchronous programming is a powerful form of concurrency, but until recently, it required extremely complex code. The async and await support in modern languages make asynchronous programming almost as easy as synchronous (nonconcurrent) programming.

异步编程是一种强大的并发形式,但直到最近,它还需要非常复杂的代码。现代语言中的async和await支持使得异步编程几乎与同步(非并发)编程一样容易。

Another form of concurrency is reactive programming. Asynchronous programming implies that the application will start an operation that will complete once at a later time. Reactive programming is closely related to asynchronous programming but is built on asynchronous events instead of asynchronous operations. Asynchronous events may not have an actual “start,” may happen at any time, and may be raised multiple times. One example is user input.

另一种并发形式是响应式编程。异步编程意味着应用程序将启动一个在稍后完成一次的操作。响应式编程与异步编程密切相关,但它建立在异步事件而不是异步操作的基础上。异步事件可能没有实际的“开始”,可能在任何时候发生,并可能引发多次。一个例子是用户输入。

1.5 Reactive programming:A declarative style of programming where the application reacts to events.

响应式编程:一种声明式编程风格,应用程序对事件做出响应。

If you consider an application to be a massive state machine, the application’s behavior can be described as reacting to a series of events by updating its state at each event. This isn’t as abstract or theoretical as it sounds; modern frameworks make this approach quite useful in real-world applications. Reactive programming isn’t necessarily concurrent, but it is closely related to concurrency, so this book covers the basics.

如果您认为应用程序是一个大型状态机,那么应用程序的行为可以描述为通过在每个事件中更新其状态来响应一系列事件。这并不像听起来那么抽象或理论化;现代框架使这种方法在现实应用程序中非常有用。响应式编程不一定是并发的,但它与并发密切相关,因此本书涵盖了基础知识。

Usually, a mixture of techniques is used when writing a concurrent program. Most applications at least use multithreading (via the thread pool) and asynchronous programming. Feel free to mix and match all the various forms of concurrency, using the appropriate tool for each part of the application.

通常,在编写并发程序时混合使用多种技术。大多数应用程序至少使用多线程(通过线程池)和异步编程。您可以自由地混合和匹配所有不同形式的并发,为应用程序的每个部分使用适当的工具。

2. Introduction to Asynchronous Programming 异步编程简介

Asynchronous programming has two primary benefits. The first benefit is for end-user GUI programs: asynchronous programming enables responsiveness. Everyone has used a program that temporarily locks up while it’s working; an asynchronous program can remain responsive to user input while it’s working.

异步编程有两个主要优点。第一个好处是终端用户GUI程序:异步编程支持响应性。每个人都使用过在运行过程中会暂时锁定的程序;异步程序在工作时可以保持对用户输入的响应。

The second benefit is for server-side programs: asynchronous programming enables scalability. A server application can scale somewhat just by using the thread pool, but an asynchronous server application can usually scale an order of magnitude better than that.

第二个好处是服务器端程序:异步编程支持可伸缩性。服务器应用程序可以通过使用线程池进行一定程度的扩展,但异步服务器应用程序的扩展通常要比线程池大一个数量级。

Both benefits of asynchronous programming derive from the same underlying aspect: asynchronous programming frees up a thread. For GUI programs,asynchronous programming frees up the UI thread; this permits the GUI application to remain responsive to user input. For server applications,asynchronous programming frees up request threads; this permits the server to use its threads to serve more requests.

异步编程的两个优点都来自于相同的基础方面:异步编程释放了一个线程。对于GUI程序,异步编程释放了UI线程;这允许GUI应用程序保持对用户输入的响应。对于服务器应用程序,异步编程释放请求线程;这允许服务器使用它的线程来服务更多的请求。

Modern asynchronous .NET applications use two keywords: async and await. The async keyword is added to a method declaration, and performs a double purpose: it enables the await keyword within that method and it signals the compiler to generate a state machine for that method, similar to how yield return works. An async method may return Task if it returns a value, Task if it doesn’t return a value, or any other “task-like” type, such as ValueTask. In addition, an async method may return IAsyncEnumerable or IAsyncEnumerator if it returns multiple values in an enumeration. The task-like types represent futures; they can notify the calling code when the async method completes.

现代的异步.NET应用程序使用了两个关键字:async和await。async关键字被添加到方法声明中,并执行双重目的:它启用方法中的await关键字,并向编译器发出信号,为该方法生成一个状态机,类似于yield return的工作方式。一个async方法如果返回值可能返回Task,如果不返回值可能返回Task,或者任何其他“类似任务”的类型,比如ValueTask。此外,如果一个async方法在一个枚举中返回多个值,则它可能返回IAsyncEnumerator或IAsyncEnumerator。任务类类型代表未来;它们可以在异步方法完成时通知调用代码。

With that background, let’s take a quick look at an example:

在此背景下,让我们快速浏览一个例子:

async Task DoSomethingAsync()
{
    int value = 13;
    // Asynchronously wait 1 second.
    await Task.Delay(TimeSpan.FromSeconds(1));
    value *= 2;
    // Asynchronously wait 1 second.
    await Task.Delay(TimeSpan.FromSeconds(1));
    Trace.WriteLine(value);
}

An async method begins executing synchronously, just like any other method.Within an async method, the await keyword performs an asynchronous wait on its argument. First, it checks whether the operation is already complete; if it is, it continues executing (synchronously). Otherwise, it will pause the async method and return an incomplete task. When that operation completes some time later, the async method will resume executing.

异步方法开始同步执行,就像其他方法一样。在异步方法中,await关键字对其参数执行异步等待。首先,它检查操作是否已经完成;如果是,则继续(同步地)执行。否则,它将暂停async方法并返回一个未完成的任务。当该操作在一段时间后完成时,async方法将继续执行。

You can think of an async method as having several synchronous portions,broken up by await statements. The first synchronous portion executes on whatever thread calls the method, but where do the other synchronous portions execute? The answer is a bit complicated.

您可以将异步方法看作有几个同步部分,由await语句分解。第一个同步部分在调用该方法的线程上执行,但是其他同步部分在哪里执行呢?答案有点复杂。

When you await a task (the most common scenario), a context is captured when the await decides to pause the method. This is the current SynchronizationContext unless it’s null, in which case the context is the current TaskScheduler. The method resumes executing within that captured context. Usually, this context is the UI context (if you’re on the UI thread) or the threadpool context (most other situations). If you have an ASP.NET Classic (pre-Core) application, then the context could also be an ASP.NET request context. ASP.NET Core uses the threadpool context rather than a special request context.

当您等待一个任务(最常见的场景)时,当await决定暂停该方法时,将捕获一个上下文。这是当前的SynchronizationContext,除非它是null,在这种情况下,上下文是当前的TaskScheduler。该方法在捕获的上下文中继续执行。通常,这个上下文是UI上下文(如果您在UI线程上)或线程池上下文(大多数其他情况)。如果你有一个ASP.NET Classic (pre-Core)应用程序,那么上下文也可以是ASP。网络请求上下文中。ASP.NET Core使用threadpool上下文而不是特殊的请求上下文。

So, in the preceding code, all the synchronous portions will attempt to resume on the original context. If you call DoSomethingAsync from a UI thread, each of its synchronous portions will run on that UI thread; but if you call it from a threadpool thread, each of its synchronous portions will run on any threadpool thread.

因此,在前面的代码中,所有的同步部分都将尝试在原始上下文上恢复。如果你从UI线程调用DoSomethingAsync,它的每个同步部分都将在UI线程上运行;但是,如果从一个线程池线程调用它,它的每个同步部分都将在任何线程池线程上运行。

You can avoid this default behavior by awaiting the result of the ConfigureAwait extension method and passing false for the continueOnCapturedContext parameter. The following code will start on the calling thread, and after it is paused by an await, it’ll resume on a threadpool thread:

你可以通过等待ConfigureAwait扩展方法的结果并为continueOnCapturedContext参数传递false来避免这种默认行为。下面的代码将从调用线程开始,在被await暂停后,它将在线程池线程中继续执行:

async Task DoSomethingAsync()
{
int value = 13;
// Asynchronously wait 1 second.
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
value *= 2;
// Asynchronously wait 1 second.
await Task.Delay(TimeSpan.FromSeconds(1)).ConfigureAwait(false);
Trace.WriteLine(value);
}

It’s good practice to always call ConfigureAwait in your core “library” methods, and only resume the context when you need it—in your outer “user interface” methods.

始终在你的核心“库”方法中调用ConfigureAwait是一个很好的做法,只有在你的外部“用户界面”方法中需要它时才恢复上下文。

The await keyword is not limited to working with tasks; it can work with any kind of awaitable that follows a certain pattern. As an example, the Base Class Library includes the ValueTask type, which reduces memory allocations if the result is commonly synchronous; for example, if the result can be read from an in-memory cache. ValueTask is not directly convertible to Task, but it does follow the awaitable pattern, so you can directly await it. There are other examples, and you can build your own, but most of the time await will take a Task or Task.

await关键字并不局限于处理任务;它可以与任何遵循特定模式的可等待对象一起工作。例如,基类库包含ValueTask类型,如果结果通常是同步的,则会减少内存分配;例如,如果结果可以从内存缓存中读取。ValueTask不能直接转换为Task,但是它遵循了可等待模式,所以你可以直接await它。还有其他的例子,你也可以自己构建,但大多数时候await会接受一个任务或Task。

There are two basic ways to create a Task instance. Some tasks represent actual code that a CPU has to execute; these computational tasks should be created by calling Task.Run (or TaskFactory.StartNew if you need them to run on a particular scheduler). Other tasks represent a notification; these kinds of eventbased tasks are created by TaskCompletionSource (or one of its shortcuts). Most I/O tasks use TaskCompletionSource.

有两种创建任务实例的基本方法。有些任务表示CPU必须执行的实际代码;这些计算任务应该通过调用Task来创建。(或TaskFactory运行。如果您需要它们在特定的调度程序上运行,请启动new)。其他任务表示一个通知;这类基于事件的任务是通过TaskCompletionSource(或它的一个快捷方式)创建的。大多数I/O任务使用TaskCompletionSource。

Error handling is natural with async and await. In the code snippet that follows, PossibleExceptionAsync may throw a NotSupportedException, but TrySomethingAsync can catch the exception naturally. The caught exception has its stack trace properly preserved and isn’t artificially wrapped in a TargetInvocationException or AggregateException:

错误处理在async和await中是很自然的。在下面的代码片段中,PossibleExceptionAsync可能抛出NotSupportedException异常,但TrySomethingAsync可以自然地捕获异常。被捕获的异常有它的堆栈跟踪被正确地保存,并且没有人为地包装在TargetInvocationException或AggregateException中:

async Task TrySomethingAsync()
{
    try
    {
        await PossibleExceptionAsync();
    }
    catch (NotSupportedException ex)
    {
        LogException(ex);
        throw;
    }
}


When an async method throws (or propagates) an exception, the exception is placed on its returned Task and the Task is completed. When that Task is awaited, the await operator will retrieve that exception and (re)throw it in a way such that its original stack trace is preserved. Thus, code such as the following example would work as expected if PossibleExceptionAsync was an async method:

当异步方法抛出(或传播)异常时,该异常被放置在其返回的任务上,任务就完成了。当该任务被等待时,await操作符将检索该异常,并(重新)抛出该异常,以保留其原始的堆栈跟踪。因此,如果可能的exceptionasync是一个异步方法,下面的示例代码将按预期工作:

async Task TrySomethingAsync()
{
    // The exception will end up on the Task, not thrown directly.
    Task task = PossibleExceptionAsync();
    try
    {
        // The Task's exception will be raised here, at the await.
        await task;
    }
    catch (NotSupportedException ex)
    {
        LogException(ex);
        throw;
    }
}

There’s one other important guideline when it comes to async methods: once you start using async, it’s best to allow it to grow through your code. If you call an async method, you should (eventually) await the task it returns. Resist the temptation to call Task.Wait, Task.Result, or GetAwaiter().GetResult(); doing so could cause a deadlock. Consider the following method:

当涉及到异步方法时,还有另一个重要的指导原则:一旦开始使用异步,最好允许它在代码中增长。如果你调用一个async方法,你应该(最终)等待它返回的任务。抵制调用Task.Wait的诱惑。,Task.结果,或GetAwaiter () .GetResult ();这样做可能会导致死锁。考虑以下方法:

async Task WaitAsync()
{
    // This await will capture the current context ...
    await Task.Delay(TimeSpan.FromSeconds(1));
    // ... and will attempt to resume the method here in that   context.
}
void Deadlock()
{
    // Start the delay.
    Task task = WaitAsync();
    // Synchronously block, waiting for the async method to     complete.
    task.Wait();
}

The code in this example will deadlock if called from a UI or ASP.NET Classic context because both of those contexts only allow one thread in at a time.Deadlock will call WaitAsync, which begins the delay. Deadlock then (synchronously) waits for that method to complete, blocking the context thread.

如果从UI或ASP调用此示例中的代码,则会死锁ASP . NET经典上下文,因为这两个上下文一次只允许一个线程进入。死锁将调用WaitAsync,从而开始延迟。然后,Deadlock(同步地)等待该方法完成,阻塞上下文线程。

When the delay completes, await attempts to resume WaitAsync within the captured context, but it cannot because there’s already a thread blocked in the context, and the context only allows one thread at a time. Deadlock can be prevented two ways: you can use ConfigureAwait(false) within WaitAsync (which causes await to ignore its context), or you can await the call to WaitAsync (making Deadlock into an async method).

当延迟完成时,await尝试在捕获的上下文中恢复WaitAsync,但它不能,因为上下文中已经有一个线程被阻塞了,而且上下文一次只允许一个线程。可以通过两种方式来防止死锁:你可以在WaitAsync中使用ConfigureAwait(false)(这会导致await忽略它的上下文),或者你可以await WaitAsync的调用(使死锁成为一个async方法)。

WARNING:If you use async, it’s best to use async all the way.

警告:如果你使用异步,最好一直使用异步。

For a more complete introduction to async, the online documentation that Microsoft has provided for async is fantastic; I recommend reading at least the Asynchronous Programming overview and the Task-based Asynchronous Pattern (TAP) overview. If you want to go a bit deeper, there’s also the Async in Depth documentation.

关于异步的更完整的介绍,微软提供的在线文档非常棒;我建议至少阅读异步编程概述和基于任务的异步模式(TAP)概述。如果您想深入了解,还有异步深入文档。

微软中文《异步编程概述》在线地址
ps:https://docs.microsoft.com/zh-cn/dotnet/csharp/programming-guide/concepts/async/

Asynchronous streams take the groundwork of async and await and extend it to handle multiple values. Asynchronous streams are built around the concept of asynchronous enumerables, which are like regular enumerables, except that they enable asynchronous work to be done when retrieving the next item in the sequence. This is an extremely powerful concept that Chapter 3 covers in more detail. Asynchronous streams are especially useful whenever you have a sequence of data that arrives either one at a time or in chunks. For example, if your application processes the response of an API that uses paging with limit and offset parameters, then asynchronous streams are an ideal abstraction. As of the time of this writing, asynchronous streams are only available on the newest .NET platforms.

异步流继承了异步的基础,等待并扩展它以处理多个值。异步流是围绕异步可枚举对象的概念构建的,异步可枚举对象与常规可枚举对象类似,只是它们允许在检索序列中的下一个条目时完成异步工作。这是一个非常强大的概念,第3章将详细介绍。当数据序列一次到达一个或以块的形式到达时,异步流特别有用。例如,如果您的应用程序处理使用带有限制和偏移量参数的分页的API的响应,那么异步流就是一个理想的抽象。截至撰写本文时,异步流仅在最新的.NET平台上可用。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
我想将frontend 也是用volumes,将其映射到/app/frontend目录,在/app/frontend下install以及build,如何实现 docker-compose.yml文件: version: '3' services: frontend: build: context: ./frontend dockerfile: Dockerfile ports: - 8010:80 restart: always backend: build: context: ./backend dockerfile: Dockerfile volumes: - /app/backend:/app environment: - CELERY_BROKER_URL=redis://redis:6379/0 command: python manage.py runserver 0.0.0.0:8000 ports: - 8011:8000 restart: always celery-worker: build: context: ./backend dockerfile: Dockerfile volumes: - /app/backend:/app environment: - CELERY_BROKER_URL=redis://redis:6379/0 command: celery -A server worker -l info --pool=solo --concurrency=1 depends_on: - redis - backend restart: always celery-beat: build: context: ./backend dockerfile: Dockerfile volumes: - /app/backend:/app environment: - CELERY_BROKER_URL=redis://redis:6379/0 command: celery -A server beat -l info --scheduler django_celery_beat.schedulers:DatabaseScheduler depends_on: - redis - backend restart: always redis: image: redis:latest ports: - 6379:6379 restart: always mysql: image: mysql:latest environment: - MYSQL_ROOT_PASSWORD=sacfxSql258147@ ports: - 8016:3306 volumes: - ./mysql:/var/lib/mysql restart: always frontend:dockerfile文件 FROM node:16.18.1 WORKDIR /app/frontend COPY package*.json ./ RUN npm install COPY . . RUN npm run build:prod FROM nginx:latest COPY --from=0 /app/frontend/dist/ /usr/share/nginx/html EXPOSE 80 CMD ["nginx", "-g", "daemon off;"]
最新发布
07-14

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

自己的九又四分之三站台

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值