Files
docs-rust/ch17/ch17-00-async-await.html
2026-06-22 21:27:36 +05:30

146 lines
10 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Fundamentals of Asynchronous Programming: Async, Await, Futures, and Streams</title>
</head>
<body>
<h1 id="fundamentals-of-asynchronous-programming-async-await-futures-and-streams"><a class="header" href="#fundamentals-of-asynchronous-programming-async-await-futures-and-streams">Fundamentals of Asynchronous Programming: Async, Await, Futures, and Streams</a></h1>
<p>Many operations we ask the computer to do can take a while to finish. It would
be nice if we could do something else while were waiting for those
long-running processes to complete. Modern computers offer two techniques for
working on more than one operation at a time: parallelism and concurrency. Our
programs logic, however, is written in a mostly linear fashion. Wed like to
be able to specify the operations a program should perform and points at which
a function could pause and some other part of the program could run instead,
without needing to specify up front exactly the order and manner in which each
bit of code should run. <em>Asynchronous programming</em> is an abstraction that lets
us express our code in terms of potential pausing points and eventual results
that takes care of the details of coordination for us.</p>
<p>This chapter builds on Chapter 16s use of threads for parallelism and
concurrency by introducing an alternative approach to writing code: Rusts
futures, streams, and the <code>async</code> and <code>await</code> syntax that let us express how
operations could be asynchronous, and the third-party crates that implement
asynchronous runtimes: code that manages and coordinates the execution of
asynchronous operations.</p>
<p>Lets consider an example. Say youre exporting a video youve created of a
family celebration, an operation that could take anywhere from minutes to
hours. The video export will use as much CPU and GPU power as it can. If you
had only one CPU core and your operating system didnt pause that export until
it completed—that is, if it executed the export <em>synchronously</em>—you couldnt do
anything else on your computer while that task was running. That would be a
pretty frustrating experience. Fortunately, your computers operating system
can, and does, invisibly interrupt the export often enough to let you get other
work done simultaneously.</p>
<p>Now say youre downloading a video shared by someone else, which can also take
a while but does not take up as much CPU time. In this case, the CPU has to
wait for data to arrive from the network. While you can start reading the data
once it starts to arrive, it might take some time for all of it to show up.
Even once the data is all present, if the video is quite large, it could take
at least a second or two to load it all. That might not sound like much, but
its a very long time for a modern processor, which can perform billions of
operations every second. Again, your operating system will invisibly interrupt
your program to allow the CPU to perform other work while waiting for the
network call to finish.</p>
<p>The video export is an example of a <em>CPU-bound</em> or <em>compute-bound</em> operation.
Its limited by the computers potential data processing speed within the CPU
or GPU, and how much of that speed it can dedicate to the operation. The video
download is an example of an <em>I/O-bound</em> operation, because its limited by the
speed of the computers <em>input and output</em>; it can only go as fast as the data
can be sent across the network.</p>
<p>In both of these examples, the operating systems invisible interrupts provide
a form of concurrency. That concurrency happens only at the level of the entire
program, though: the operating system interrupts one program to let other
programs get work done. In many cases, because we understand our programs at a
much more granular level than the operating system does, we can spot
opportunities for concurrency that the operating system cant see.</p>
<p>For example, if were building a tool to manage file downloads, we should be
able to write our program so that starting one download wont lock up the UI,
and users should be able to start multiple downloads at the same time. Many
operating system APIs for interacting with the network are <em>blocking</em>, though;
that is, they block the programs progress until the data theyre processing is
completely ready.</p>
<section class="note" aria-role="note">
<p>Note: This is how <em>most</em> function calls work, if you think about it. However,
the term <em>blocking</em> is usually reserved for function calls that interact with
files, the network, or other resources on the computer, because those are the
cases where an individual program would benefit from the operation being
<em>non</em>-blocking.</p>
</section>
<p>We could avoid blocking our main thread by spawning a dedicated thread to
download each file. However, the overhead of the system resources used by those
threads would eventually become a problem. It would be preferable if the call
didnt block in the first place, and instead we could define a number of tasks
that wed like our program to complete and allow the runtime to choose the best
order and manner in which to run them.</p>
<p>That is exactly what Rusts <em>async</em> (short for <em>asynchronous</em>) abstraction
gives us. In this chapter, youll learn all about async as we cover the
following topics:</p>
<ul>
<li>How to use Rusts <code>async</code> and <code>await</code> syntax and execute asynchronous
functions with a runtime</li>
<li>How to use the async model to solve some of the same challenges we looked at
in Chapter 16</li>
<li>How multithreading and async provide complementary solutions that you can
combine in many cases</li>
</ul>
<p>Before we see how async works in practice, though, we need to take a short
detour to discuss the differences between parallelism and concurrency.</p>
<h2 id="parallelism-and-concurrency"><a class="header" href="#parallelism-and-concurrency">Parallelism and Concurrency</a></h2>
<p>Weve treated parallelism and concurrency as mostly interchangeable so far. Now
we need to distinguish between them more precisely, because the differences
will show up as we start working.</p>
<p>Consider the different ways a team could split up work on a software project.
You could assign a single member multiple tasks, assign each member one task,
or use a mix of the two approaches.</p>
<p>When an individual works on several different tasks before any of them is
complete, this is <em>concurrency</em>. One way to implement concurrency is similar to
having two different projects checked out on your computer, and when you get
bored or stuck on one project, you switch to the other. Youre just one person,
so you cant make progress on both tasks at the exact same time, but you can
multitask, making progress on one at a time by switching between them (see
Figure 17-1).</p>
<figure>
<img src="../img/trpl17-01.svg" class="center" alt="A diagram with stacked boxes labeled Task A and Task B, with diamonds in them representing subtasks. Arrows point from A1 to B1, B1 to A2, A2 to B2, B2 to A3, A3 to A4, and A4 to B3. The arrows between the subtasks cross the boxes between Task A and Task B." />
<figcaption>Figure 17-1: A concurrent workflow, switching between Task A and Task B</figcaption>
</figure>
<p>When the team splits up a group of tasks by having each member take one task
and work on it alone, this is <em>parallelism</em>. Each person on the team can make
progress at the exact same time (see Figure 17-2).</p>
<figure>
<img src="../img/trpl17-02.svg" class="center" alt="A diagram with stacked boxes labeled Task A and Task B, with diamonds in them representing subtasks. Arrows point from A1 to A2, A2 to A3, A3 to A4, B1 to B2, and B2 to B3. No arrows cross between the boxes for Task A and Task B." />
<figcaption>Figure 17-2: A parallel workflow, where work happens on Task A and Task B independently</figcaption>
</figure>
<p>In both of these workflows, you might have to coordinate between different
tasks. Maybe you thought the task assigned to one person was totally
independent from everyone elses work, but it actually requires another person
on the team to finish their task first. Some of the work could be done in
parallel, but some of it was actually <em>serial</em>: it could only happen in a
series, one task after the other, as in Figure 17-3.</p>
<figure>
<img src="../img/trpl17-03.svg" class="center" alt="A diagram with stacked boxes labeled Task A and Task B, with diamonds in them representing subtasks. In Task A, arrows point from A1 to A2, from A2 to a pair of thick vertical lines like a “pause” symbol, and from that symbol to A3. In task B, arrows point from B1 to B2, from B2 to B3, from B3 to A3, and from B3 to B4." />
<figcaption>Figure 17-3: A partially parallel workflow, where work happens on Task A and Task B independently until Task A3 is blocked on the results of Task B3.</figcaption>
</figure>
<p>Likewise, you might realize that one of your own tasks depends on another of
your tasks. Now your concurrent work has also become serial.</p>
<p>Parallelism and concurrency can intersect with each other, too. If you learn
that a colleague is stuck until you finish one of your tasks, youll probably
focus all your efforts on that task to “unblock” your colleague. You and your
coworker are no longer able to work in parallel, and youre also no longer able
to work concurrently on your own tasks.</p>
<p>The same basic dynamics come into play with software and hardware. On a machine
with a single CPU core, the CPU can perform only one operation at a time, but
it can still work concurrently. Using tools such as threads, processes, and
async, the computer can pause one activity and switch to others before
eventually cycling back to that first activity again. On a machine with
multiple CPU cores, it can also do work in parallel. One core can be performing
one task while another core performs a completely unrelated one, and those
operations actually happen at the same time.</p>
<p>Running async code in Rust usually happens concurrently. Depending on the
hardware, the operating system, and the async runtime we are using (more on
async runtimes shortly), that concurrency may also use parallelism under the
hood.</p>
<p>Now, lets dive into how async programming in Rust actually works.</p>
</body>
</html>