WEBVTT

1
00:00:00.160 --> 00:00:02.879
<v Speaker 1>Welcome to the deep dive. Today, we are getting into

2
00:00:02.960 --> 00:00:06.559
<v Speaker 1>something that, well, if you're a developer, it has probably

3
00:00:06.639 --> 00:00:09.080
<v Speaker 1>kept you up at night at least once. We're talking

4
00:00:09.080 --> 00:00:11.519
<v Speaker 1>about modern concurrency and sea shark.

5
00:00:11.679 --> 00:00:14.439
<v Speaker 2>Yeah, it's it's a massive topic.

6
00:00:14.240 --> 00:00:16.960
<v Speaker 1>It really is. And we are pulling our insights today

7
00:00:17.000 --> 00:00:20.519
<v Speaker 1>from some excerpts of Stephen Cleary's Concurrency and c Shark cookbook.

8
00:00:21.039 --> 00:00:24.879
<v Speaker 1>Our mission here is to really just extract the critical

9
00:00:24.920 --> 00:00:29.399
<v Speaker 1>knowledge you need to master responsive scalable programming, you know,

10
00:00:29.480 --> 00:00:32.520
<v Speaker 1>without getting bogged down by the absolute nightmares of old

11
00:00:32.520 --> 00:00:34.000
<v Speaker 1>school multi threading, right.

12
00:00:33.880 --> 00:00:35.799
<v Speaker 2>Because nobody wants to go back to those nightmares. I

13
00:00:35.799 --> 00:00:36.759
<v Speaker 2>mean it's just it's.

14
00:00:36.640 --> 00:00:39.119
<v Speaker 1>Rough, definitely not. But before we get into the code,

15
00:00:39.119 --> 00:00:40.920
<v Speaker 1>I have to talk about the cover of this book.

16
00:00:41.280 --> 00:00:43.880
<v Speaker 1>If you haven't seen it, you might expect, I don't know,

17
00:00:44.079 --> 00:00:46.880
<v Speaker 1>a majestic animal like maybe a falcon or a cheetah,

18
00:00:47.240 --> 00:00:49.159
<v Speaker 1>something to represent speed and raw power.

19
00:00:49.280 --> 00:00:52.200
<v Speaker 2>Yeah, that would make sense for a book on concurrency.

20
00:00:51.799 --> 00:00:54.520
<v Speaker 1>Right, But instead you get a common palms of bit.

21
00:00:55.079 --> 00:00:58.079
<v Speaker 1>And if you read the author's own preface, he actually

22
00:00:58.079 --> 00:01:00.960
<v Speaker 1>points out that this animal is widely considered like it

23
00:01:00.960 --> 00:01:03.880
<v Speaker 1>gets into attics, it poops everywhere, and it just makes

24
00:01:03.920 --> 00:01:08.680
<v Speaker 1>these incredibly loud, crashing noises at the absolute worst possible times.

25
00:01:08.799 --> 00:01:12.120
<v Speaker 2>And there's another really funny detail about the civet. It

26
00:01:12.200 --> 00:01:15.200
<v Speaker 2>eats coffee cherries and well passes the beans through its

27
00:01:15.200 --> 00:01:19.079
<v Speaker 2>digestive tract, which is just ea yeah, but those excreted

28
00:01:19.159 --> 00:01:22.840
<v Speaker 2>beans are actually harvested to make Kobe Luwac, which is

29
00:01:22.959 --> 00:01:25.680
<v Speaker 2>notoriously one of the most expensive coffees in the world.

30
00:01:26.159 --> 00:01:29.239
<v Speaker 2>Yet and this is the best part. The Specialty Coffee

31
00:01:29.280 --> 00:01:33.040
<v Speaker 2>Association officially states that it quote just tastes bad.

32
00:01:33.599 --> 00:01:36.680
<v Speaker 1>Oh wow. So it's expensive but awful exactly.

33
00:01:36.840 --> 00:01:40.760
<v Speaker 2>And the author chose this mascot for a very specific, deliberate.

34
00:01:40.359 --> 00:01:43.280
<v Speaker 1>Reason because to anyone who has been in the trenches,

35
00:01:43.319 --> 00:01:46.599
<v Speaker 1>to the uninitiated, old school multi threading is exactly like

36
00:01:46.599 --> 00:01:48.359
<v Speaker 1>the civet. I mean, it is a messy pest. It

37
00:01:48.400 --> 00:01:52.239
<v Speaker 1>causes loud, unpredictable crashes. And this crashes usually happen you know,

38
00:01:52.400 --> 00:01:53.200
<v Speaker 1>the value moment you.

39
00:01:53.120 --> 00:01:55.280
<v Speaker 2>Deployed at production yep, always in production.

40
00:01:55.159 --> 00:01:58.000
<v Speaker 1>Always and after all the time, the money, the sleepless

41
00:01:58.079 --> 00:02:00.879
<v Speaker 1>nights you spend trying to fix it, it leaves a

42
00:02:00.879 --> 00:02:02.040
<v Speaker 1>really bad taste in your mouth.

43
00:02:02.120 --> 00:02:04.200
<v Speaker 2>It really does. And I think the mission of our

44
00:02:04.239 --> 00:02:07.319
<v Speaker 2>deep dive today is to show you how modern c

45
00:02:07.480 --> 00:02:11.000
<v Speaker 2>shark has just completely evolved past that mess. We are

46
00:02:11.039 --> 00:02:14.479
<v Speaker 2>moving away from writing strict, step by step instructions that

47
00:02:14.639 --> 00:02:20.039
<v Speaker 2>forcefully micromanage your system resources, and we're moving toward well

48
00:02:20.319 --> 00:02:24.639
<v Speaker 2>choreographing a self balancing digital ecosystem. It is a fundamental

49
00:02:24.719 --> 00:02:27.039
<v Speaker 2>shift in how we architect applications today.

50
00:02:27.159 --> 00:02:30.080
<v Speaker 1>Okay, let's unpack this because the author he really doesn't

51
00:02:30.080 --> 00:02:33.919
<v Speaker 1>pull any punches regarding that shift. There's this brutal, just

52
00:02:34.120 --> 00:02:36.919
<v Speaker 1>instantly memorable quote early in the text. It simply says,

53
00:02:37.080 --> 00:02:39.159
<v Speaker 1>as soon as you type new thread, it's over. Your

54
00:02:39.159 --> 00:02:40.919
<v Speaker 1>project already has legacy code.

55
00:02:41.120 --> 00:02:43.479
<v Speaker 2>Ouch. Yeah, which is I mean, that's a tough pill

56
00:02:43.520 --> 00:02:46.520
<v Speaker 2>to swallow. If you grew up manually spinning up threads.

57
00:02:46.599 --> 00:02:49.280
<v Speaker 1>It feels wrong, It feels like you're losing control right.

58
00:02:49.199 --> 00:02:53.120
<v Speaker 2>Exactly, But it requires us to really look at what

59
00:02:53.199 --> 00:02:55.680
<v Speaker 2>the run time is actually doing. Now we have to

60
00:02:55.680 --> 00:02:58.960
<v Speaker 2>clarify the terminology here. Multi threading is just one form

61
00:02:59.000 --> 00:03:03.639
<v Speaker 2>of concurrency, the direct manual management of threads, you know,

62
00:03:03.800 --> 00:03:07.360
<v Speaker 2>allocating them, destroying them, trying to figure out exactly how

63
00:03:07.400 --> 00:03:10.479
<v Speaker 2>many your specific hardware can handle that is dead.

64
00:03:10.560 --> 00:03:13.120
<v Speaker 1>But the threads of themselves aren't gone obviously.

65
00:03:12.759 --> 00:03:15.599
<v Speaker 2>Right right, They're just abstracted. They live on behind the

66
00:03:15.639 --> 00:03:19.199
<v Speaker 2>thread pool under the hood. And the modern net threadpool

67
00:03:19.280 --> 00:03:22.560
<v Speaker 2>is this highly optimized dynamic heuristic algorithm.

68
00:03:22.639 --> 00:03:23.879
<v Speaker 1>So it's making decisions for you.

69
00:03:24.080 --> 00:03:28.520
<v Speaker 2>Yeah, constantly. It monitors the CPU load, the memory constrains,

70
00:03:28.560 --> 00:03:32.439
<v Speaker 2>the queue of pending work, and it just continuously adjusts

71
00:03:32.439 --> 00:03:34.000
<v Speaker 2>the number of active threads on the fly. I mean,

72
00:03:34.039 --> 00:03:37.319
<v Speaker 2>it is vastly better at managing resource allocation than any

73
00:03:37.560 --> 00:03:39.159
<v Speaker 2>custom code you or I could ever write.

74
00:03:39.159 --> 00:03:41.759
<v Speaker 1>It's kind of like, I mean, think about legacy threading

75
00:03:41.759 --> 00:03:44.639
<v Speaker 1>like manually moving the gears inside your car's transmission by hand,

76
00:03:44.680 --> 00:03:48.280
<v Speaker 1>just constantly grinding them, whereas modern concurrency is like an

77
00:03:48.360 --> 00:03:51.479
<v Speaker 1>automatic transmission that dynamically adjusts to the traffic around you.

78
00:03:51.800 --> 00:03:54.719
<v Speaker 2>That's a really good analogy. Yeah, but just handing over

79
00:03:54.759 --> 00:03:57.520
<v Speaker 2>thread allocation to the run time, you know, that isn't

80
00:03:57.599 --> 00:04:00.360
<v Speaker 2>enough to prevent the classic nightmare scenario.

81
00:04:00.199 --> 00:04:02.520
<v Speaker 1>Like deadlocks and race conditions exactly.

82
00:04:03.000 --> 00:04:06.919
<v Speaker 2>The source material emphasizes that modern concurrency heavily relies on

83
00:04:07.000 --> 00:04:08.560
<v Speaker 2>adopting a functional mindset.

84
00:04:08.680 --> 00:04:11.599
<v Speaker 1>Okay, so what does that actually mean in practice, Well.

85
00:04:11.439 --> 00:04:16.199
<v Speaker 2>It's the real secret sauce. Modern concurrency strongly leans toward

86
00:04:16.279 --> 00:04:21.720
<v Speaker 2>writing pure code. That means methods that take inputs, produce outputs,

87
00:04:21.839 --> 00:04:25.319
<v Speaker 2>and have zero side effects. And you combine that with

88
00:04:25.519 --> 00:04:27.560
<v Speaker 2>immutable data structures.

89
00:04:27.160 --> 00:04:30.120
<v Speaker 1>Meaning data that literally cannot be changed in memory once

90
00:04:30.160 --> 00:04:31.040
<v Speaker 1>it has been created.

91
00:04:31.199 --> 00:04:34.399
<v Speaker 2>Right, So if the data can't change, multiple threads can

92
00:04:34.439 --> 00:04:39.120
<v Speaker 2>read it simultaneously without needing complex locks. You inherently bypass

93
00:04:39.199 --> 00:04:43.000
<v Speaker 2>the synchronization issues entirely because well, there's nothing to synchronize.

94
00:04:43.040 --> 00:04:45.480
<v Speaker 1>Wow, okay, so you eliminate shared state, and when you

95
00:04:45.519 --> 00:04:49.759
<v Speaker 1>eliminate shared mutable state, those classic multi threading bugs just sort.

96
00:04:49.519 --> 00:04:51.560
<v Speaker 2>Of evaporate, they do. They just go away.

97
00:04:51.759 --> 00:04:55.600
<v Speaker 1>So, if we aren't creating threads manually anymore, how do

98
00:04:55.759 --> 00:04:58.560
<v Speaker 1>we handle tasks that take time? Like how do we

99
00:04:58.720 --> 00:05:02.319
<v Speaker 1>wait for external cs stems to respond without locking up

100
00:05:02.360 --> 00:05:03.399
<v Speaker 1>our entire application?

101
00:05:03.680 --> 00:05:06.680
<v Speaker 2>That takes us right into the mechanics of asynchronous programming,

102
00:05:07.360 --> 00:05:11.639
<v Speaker 2>the async and await keywords. Asynchronous programming is fundamentally about

103
00:05:11.680 --> 00:05:16.040
<v Speaker 2>the art of waiting without blocking. In dot net, we

104
00:05:16.199 --> 00:05:19.879
<v Speaker 2>use objects like task to represent a future. It's basically

105
00:05:19.920 --> 00:05:22.439
<v Speaker 2>an operation that will complete at some point. When you

106
00:05:22.560 --> 00:05:25.240
<v Speaker 2>use the awight keyword on a task, you are telling

107
00:05:25.279 --> 00:05:28.240
<v Speaker 2>the runtime, Hey, I need the result of this operation,

108
00:05:28.600 --> 00:05:30.319
<v Speaker 2>but I don't need to hold onto the current thread

109
00:05:30.319 --> 00:05:30.959
<v Speaker 2>while I wait.

110
00:05:31.000 --> 00:05:33.560
<v Speaker 1>So the thread is just instantly returned to the threadpool

111
00:05:33.600 --> 00:05:34.720
<v Speaker 1>to do other useful work.

112
00:05:34.800 --> 00:05:36.720
<v Speaker 2>Exactly, and on the surface, you should care about this

113
00:05:36.759 --> 00:05:39.480
<v Speaker 2>because it has huge benefits. On the client side, it

114
00:05:39.560 --> 00:05:43.279
<v Speaker 2>keeps your UIs totally responsive, the app doesn't freeze. On

115
00:05:43.360 --> 00:05:47.000
<v Speaker 2>the server side, it massively increases scalability.

116
00:05:46.399 --> 00:05:49.600
<v Speaker 1>Because an asp net server isn't just burning through its

117
00:05:49.639 --> 00:05:53.519
<v Speaker 1>finite pool of threads staring blankly at a database connection

118
00:05:53.639 --> 00:05:54.800
<v Speaker 1>waiting for a response.

119
00:05:54.920 --> 00:05:57.040
<v Speaker 2>Right, it can serve other users in the meantime.

120
00:05:57.240 --> 00:05:59.199
<v Speaker 1>But wait, let me push back on this a little bit.

121
00:06:00.079 --> 00:06:03.519
<v Speaker 1>If a weight pauses a method and completely lets go

122
00:06:03.600 --> 00:06:06.199
<v Speaker 1>of the threads so it isn't blocked, where does the

123
00:06:06.240 --> 00:06:09.160
<v Speaker 1>rest of the code actually execute when it wakes up? Like,

124
00:06:09.240 --> 00:06:12.000
<v Speaker 1>how does the local state survive if the thread is

125
00:06:12.079 --> 00:06:12.839
<v Speaker 1>just gone?

126
00:06:13.120 --> 00:06:17.399
<v Speaker 2>That is where the compiler performs some absolute magic. When

127
00:06:17.399 --> 00:06:20.560
<v Speaker 2>you mark a method as async, the c sharp compiler

128
00:06:20.600 --> 00:06:23.600
<v Speaker 2>actually rips that method apart. It rewrites it into a

129
00:06:23.639 --> 00:06:25.560
<v Speaker 2>complex state machine behind the scenes.

130
00:06:25.680 --> 00:06:27.199
<v Speaker 1>Literally rewrites your code.

131
00:06:27.519 --> 00:06:30.480
<v Speaker 2>Literally, it takes all your local variables, which would normally

132
00:06:30.519 --> 00:06:33.120
<v Speaker 2>live on the thread stack and be destroyed when the

133
00:06:33.120 --> 00:06:36.040
<v Speaker 2>thread is released, and it elevates them into fields on

134
00:06:36.160 --> 00:06:38.439
<v Speaker 2>a hidden class that's allocated on the heap.

135
00:06:38.519 --> 00:06:40.959
<v Speaker 1>Okay, so the run time is basically leaving a bookmark

136
00:06:41.120 --> 00:06:44.160
<v Speaker 1>complete with all the local environment variables saved safely in

137
00:06:44.199 --> 00:06:45.480
<v Speaker 1>memory before it steps away.

138
00:06:45.560 --> 00:06:49.639
<v Speaker 2>Yes, and along with that state machine, it captures something crucial.

139
00:06:49.680 --> 00:06:53.759
<v Speaker 2>It's called the synchronization context. Think of the synchronization context

140
00:06:53.839 --> 00:06:56.319
<v Speaker 2>as the specific environment the code was running in before

141
00:06:56.319 --> 00:06:56.839
<v Speaker 2>it paused.

142
00:06:56.879 --> 00:06:57.839
<v Speaker 1>Give you an example of that.

143
00:06:58.160 --> 00:07:00.680
<v Speaker 2>So if you click a button in a window interface,

144
00:07:01.240 --> 00:07:04.480
<v Speaker 2>you are on the UI thread and the context that

145
00:07:04.560 --> 00:07:07.759
<v Speaker 2>gets captured represents that specific UI environment.

146
00:07:07.920 --> 00:07:09.720
<v Speaker 1>Okay, I think I have a way to visualize this.

147
00:07:10.240 --> 00:07:11.759
<v Speaker 1>It's like a restaurant heirstas.

148
00:07:11.959 --> 00:07:12.839
<v Speaker 2>Okay, let's hear it.

149
00:07:12.920 --> 00:07:15.160
<v Speaker 1>You have a table, but you need to step outside

150
00:07:15.160 --> 00:07:18.319
<v Speaker 1>to take a phone call. The hostess doesn't just leave

151
00:07:18.360 --> 00:07:21.079
<v Speaker 1>your table empty for an hour while a huge line

152
00:07:21.160 --> 00:07:22.079
<v Speaker 1>forms at the door.

153
00:07:22.360 --> 00:07:22.600
<v Speaker 2>Right.

154
00:07:22.959 --> 00:07:26.000
<v Speaker 1>She gives the table to someone else, but she captures

155
00:07:26.000 --> 00:07:29.000
<v Speaker 1>your context. She remembers exactly what you ordered and exactly

156
00:07:29.040 --> 00:07:31.160
<v Speaker 1>where you were sitting right right, So when you finish

157
00:07:31.240 --> 00:07:34.120
<v Speaker 1>your call and come back inside, she uses that context

158
00:07:34.160 --> 00:07:36.399
<v Speaker 1>to seat you at an appropriate table to resume your

159
00:07:36.439 --> 00:07:37.839
<v Speaker 1>meal right where you left off.

160
00:07:37.920 --> 00:07:40.000
<v Speaker 2>That is a brilliant way to look at it. Yeah.

161
00:07:40.279 --> 00:07:44.560
<v Speaker 2>When the asynchronous operation, like say a database call, finally finishes,

162
00:07:44.920 --> 00:07:49.120
<v Speaker 2>the state machine uses that captured synchronization context to route

163
00:07:49.120 --> 00:07:51.759
<v Speaker 2>the execution back to where it belongs. Got it So

164
00:07:51.800 --> 00:07:54.439
<v Speaker 2>if it captured a UI context, it cues the rest

165
00:07:54.439 --> 00:07:57.399
<v Speaker 2>of the method to resume specifically on the UI thread.

166
00:07:57.480 --> 00:07:59.879
<v Speaker 2>And that allows you to safely update a textbox or

167
00:08:00.160 --> 00:08:02.959
<v Speaker 2>progress bar without getting cross thread exceptions.

168
00:08:03.160 --> 00:08:05.759
<v Speaker 1>And the text gives some very practical ways to wield

169
00:08:05.759 --> 00:08:09.839
<v Speaker 1>this right, like task delay. Instead of the legacy approach

170
00:08:09.879 --> 00:08:13.639
<v Speaker 1>of thread sleep, which let's be honest, violently blocks a

171
00:08:13.639 --> 00:08:17.399
<v Speaker 1>thread and ruins your scalability, task delay simply sets a timer,

172
00:08:17.920 --> 00:08:20.160
<v Speaker 1>yields the thread back to the pool, and then uses

173
00:08:20.199 --> 00:08:22.560
<v Speaker 1>that state machine to resume when the time is up.

174
00:08:22.680 --> 00:08:26.439
<v Speaker 2>Yeah, and you can orchestrate really complex workflows using combinators

175
00:08:26.920 --> 00:08:30.000
<v Speaker 2>like task dot went all that takes an entire collection

176
00:08:30.040 --> 00:08:32.559
<v Speaker 2>of tasks and gives you back a single task that

177
00:08:32.600 --> 00:08:36.200
<v Speaker 2>completes only when every underlying operation finishes.

178
00:08:35.960 --> 00:08:39.120
<v Speaker 1>Which is perfect for modern micro services. You might need

179
00:08:39.159 --> 00:08:43.200
<v Speaker 1>to gather data from an inventory API, a pricing API,

180
00:08:43.320 --> 00:08:46.120
<v Speaker 1>and a user profile API all at the same time

181
00:08:46.159 --> 00:08:48.639
<v Speaker 1>before you can render a single checkout page exactly.

182
00:08:49.039 --> 00:08:51.159
<v Speaker 2>And the flip side of that, which is equally useful

183
00:08:51.240 --> 00:08:53.679
<v Speaker 2>is task When anything, you give it a bunch of tasks,

184
00:08:53.879 --> 00:08:55.879
<v Speaker 2>but it completes the moment the first one finishes.

185
00:08:55.960 --> 00:08:58.720
<v Speaker 1>Oh right. The book uses the example of pinging multiple

186
00:08:58.720 --> 00:09:02.639
<v Speaker 1>stock quote web service simultaneously. You only care about the

187
00:09:02.639 --> 00:09:05.039
<v Speaker 1>fastest response to give your user the real time price,

188
00:09:05.120 --> 00:09:06.720
<v Speaker 1>so you just take the first one that comes back

189
00:09:06.759 --> 00:09:07.759
<v Speaker 1>and discard the rest.

190
00:09:08.279 --> 00:09:13.480
<v Speaker 2>Right. Super elegant, but as powerful as this aweight and

191
00:09:13.759 --> 00:09:18.720
<v Speaker 2>synchronization context mechanism is, the text really emphasizes that we

192
00:09:18.759 --> 00:09:21.559
<v Speaker 2>are stepping into a very dangerous performance trap if we

193
00:09:21.559 --> 00:09:23.120
<v Speaker 2>don't understand what's happening under the hood.

194
00:09:23.159 --> 00:09:26.440
<v Speaker 1>Okay, here's where it gets really interesting because I actually

195
00:09:26.519 --> 00:09:29.360
<v Speaker 1>struggled with this exact concept when I was reading the source.

196
00:09:29.519 --> 00:09:30.799
<v Speaker 2>Oh yeah, yeah.

197
00:09:31.039 --> 00:09:33.679
<v Speaker 1>If capturing the context is what makes a sinc weate

198
00:09:33.759 --> 00:09:36.960
<v Speaker 1>so seamless, why does the book treat it like a hazard.

199
00:09:37.200 --> 00:09:39.720
<v Speaker 1>Like if I'm building an application, why wouldn't I always

200
00:09:39.720 --> 00:09:42.000
<v Speaker 1>want my code to resume right where it left off.

201
00:09:42.320 --> 00:09:45.759
<v Speaker 1>Jumping around between random background threads seems super confusing.

202
00:09:45.879 --> 00:09:47.840
<v Speaker 2>It does seem confusing at first. Yeah, you have to

203
00:09:47.840 --> 00:09:50.759
<v Speaker 2>look at the physical bottleneck of the UI thread. The

204
00:09:50.840 --> 00:09:54.240
<v Speaker 2>UI thread is just one thungal thread. Capturing and resuming

205
00:09:54.279 --> 00:09:57.240
<v Speaker 2>on that context requires the run time to package up

206
00:09:57.240 --> 00:10:00.519
<v Speaker 2>a continuation message and literally shove it into the UI

207
00:10:00.519 --> 00:10:03.399
<v Speaker 2>thread's message pump. Okay, and the team that built the

208
00:10:03.399 --> 00:10:06.759
<v Speaker 2>Windows runtime actually quantified this exact.

209
00:10:06.320 --> 00:10:09.279
<v Speaker 1>Overhead right the paper cuts issue the one hundred versus

210
00:10:09.360 --> 00:10:12.080
<v Speaker 1>one thousand continuations metric exactly.

211
00:10:12.559 --> 00:10:14.879
<v Speaker 2>They found that if your application is routing about one

212
00:10:15.039 --> 00:10:18.879
<v Speaker 2>hundred continuations per second back to the UI thread, it's

213
00:10:18.879 --> 00:10:22.279
<v Speaker 2>totally fine. The thread can handle drawing the screen and

214
00:10:22.320 --> 00:10:25.240
<v Speaker 2>processing those resume messages. But if you hit around one

215
00:10:25.279 --> 00:10:28.720
<v Speaker 2>thousand continuations per second, you completely flood the message pump.

216
00:10:29.080 --> 00:10:32.480
<v Speaker 2>Oh wow, Yeah, the UI thread becomes so overwhelmed just

217
00:10:32.639 --> 00:10:36.200
<v Speaker 2>processing resume messages that it actually stops drawing the screen.

218
00:10:36.720 --> 00:10:41.519
<v Speaker 2>The application freezes. It's literally death by a thousand paper cuts.

219
00:10:41.840 --> 00:10:44.440
<v Speaker 1>So if I'm writing a core library, say a data

220
00:10:44.480 --> 00:10:47.360
<v Speaker 1>access layer that loops through thousands of records and makes

221
00:10:47.360 --> 00:10:49.879
<v Speaker 1>internal asynchronous calls, and I allow every single one of

222
00:10:49.919 --> 00:10:52.440
<v Speaker 1>those weights to capture the UI context. I'm going to

223
00:10:52.440 --> 00:10:55.159
<v Speaker 1>accidentally lock up the screens of whoever uses my library.

224
00:10:55.240 --> 00:10:57.879
<v Speaker 2>Yes, you'll freeze their app. And the fix for this

225
00:10:58.000 --> 00:11:01.759
<v Speaker 2>is a method called configure weight false. When you pend

226
00:11:01.799 --> 00:11:04.399
<v Speaker 2>to configure weight false to an awaitable task, you are

227
00:11:04.480 --> 00:11:06.480
<v Speaker 2>explicitly overwriting the state machine.

228
00:11:06.519 --> 00:11:09.000
<v Speaker 1>You're telling it to ignore the hostess exactly.

229
00:11:09.320 --> 00:11:12.440
<v Speaker 2>You are telling the compiler do not bother capturing the context.

230
00:11:12.720 --> 00:11:16.480
<v Speaker 2>When this external operation finishes, just grab any available background

231
00:11:16.519 --> 00:11:19.919
<v Speaker 2>thread from the threadpool and continue executing.

232
00:11:19.480 --> 00:11:23.399
<v Speaker 1>Which entirely skips the UI message pump bottleneck. So the

233
00:11:23.480 --> 00:11:27.200
<v Speaker 1>rule of thumb is use configure a weight false rigorously

234
00:11:27.279 --> 00:11:30.720
<v Speaker 1>in all your core library and background code, and only

235
00:11:30.759 --> 00:11:34.440
<v Speaker 1>rely on the default context capturing behavior in your outermost

236
00:11:34.559 --> 00:11:37.519
<v Speaker 1>user interface layer. You know where you actually need to

237
00:11:37.519 --> 00:11:38.519
<v Speaker 1>touch visual elements.

238
00:11:38.519 --> 00:11:41.519
<v Speaker 2>That's the exact architectural boundary you want to establish. But yeah,

239
00:11:41.639 --> 00:11:44.080
<v Speaker 2>configure way is really just a performance optimization. There is

240
00:11:44.120 --> 00:11:47.480
<v Speaker 2>a much bigger trap mentioned in the sources, something that

241
00:11:47.519 --> 00:11:50.720
<v Speaker 2>will flat out destroy your application. Oh boy, the ultimate

242
00:11:50.759 --> 00:11:54.080
<v Speaker 2>sin of modern c shark concurrency ACYNC void.

243
00:11:54.600 --> 00:11:57.879
<v Speaker 1>Yeah, the book practically puts a skull and crossbones next

244
00:11:57.919 --> 00:12:00.960
<v Speaker 1>to ACYNC void. But let's clarify why it even exists.

245
00:12:01.000 --> 00:12:03.960
<v Speaker 1>If it's so incredibly dangerous, why did Microsoft put it

246
00:12:04.000 --> 00:12:05.159
<v Speaker 1>in the language in the first place.

247
00:12:05.200 --> 00:12:08.960
<v Speaker 2>Well, they had to purely for historical backward compatibility. Think

248
00:12:08.960 --> 00:12:12.720
<v Speaker 2>about a classic Windows Forms or WPF application. You have

249
00:12:12.759 --> 00:12:16.039
<v Speaker 2>a button click event handler, right. The signature for that event,

250
00:12:16.159 --> 00:12:19.559
<v Speaker 2>which was mandated by the framework years before acing programming

251
00:12:19.559 --> 00:12:25.039
<v Speaker 2>ever existed, is private void button click. It absolutely must

252
00:12:25.159 --> 00:12:27.519
<v Speaker 2>return void. But what if you want to await a

253
00:12:27.600 --> 00:12:30.200
<v Speaker 2>database call when the user clicks that button.

254
00:12:30.320 --> 00:12:32.720
<v Speaker 1>You can't just change the signature to return a task,

255
00:12:32.960 --> 00:12:35.919
<v Speaker 1>or the framework won't recognize it as an event handler anymore.

256
00:12:36.039 --> 00:12:39.240
<v Speaker 2>Exactly. So, acinc void was introduced as a compromise just

257
00:12:39.279 --> 00:12:42.960
<v Speaker 2>to allow developers to use the await keyword inside legacy

258
00:12:42.960 --> 00:12:47.080
<v Speaker 2>event handlers. But doing so breaks the fundamental air handling

259
00:12:47.120 --> 00:12:49.080
<v Speaker 2>mechanics of the modern language.

260
00:12:48.679 --> 00:12:51.960
<v Speaker 1>Because there's no task object. Let's trace the mechanics of

261
00:12:52.000 --> 00:12:55.080
<v Speaker 1>a failure here, because this is fascinating. When you write

262
00:12:55.080 --> 00:12:58.519
<v Speaker 1>a proper acinc task method and something goes wrong, say

263
00:12:58.600 --> 00:13:01.360
<v Speaker 1>the network just drops what happened to the exception okay.

264
00:13:01.399 --> 00:13:03.960
<v Speaker 2>So normally, the run time catches that exception and neatly

265
00:13:04.000 --> 00:13:06.799
<v Speaker 2>packages it inside the task object that is returned to

266
00:13:06.840 --> 00:13:10.200
<v Speaker 2>the collar. The exception just sits there, completely harmless, until

267
00:13:10.200 --> 00:13:13.720
<v Speaker 2>the calling code actually awaits the task. At that exact moment,

268
00:13:14.000 --> 00:13:17.240
<v Speaker 2>the state machine unwraps the exception and rethrows it, allowing

269
00:13:17.279 --> 00:13:19.960
<v Speaker 2>you to catch it gracefully in a standard try catch block.

270
00:13:20.159 --> 00:13:22.759
<v Speaker 1>But if the method is async void, there is no

271
00:13:23.279 --> 00:13:26.080
<v Speaker 1>task object to act as that protective wrapper.

272
00:13:25.919 --> 00:13:28.600
<v Speaker 2>Right, There's nowhere to store the error. So when an

273
00:13:28.639 --> 00:13:32.080
<v Speaker 2>exception is thrown inside an ACYNC void method, it bypasses

274
00:13:32.080 --> 00:13:34.039
<v Speaker 2>any triketch block you might have set up. It just

275
00:13:34.080 --> 00:13:38.039
<v Speaker 2>blows right past it. The exception escapes the method entirely

276
00:13:38.360 --> 00:13:41.519
<v Speaker 2>and is posted directly to the ambient synchronization context.

277
00:13:41.799 --> 00:13:42.960
<v Speaker 1>And well that's then.

278
00:13:43.200 --> 00:13:47.399
<v Speaker 2>Well. In almost every modern dot net environment, an unhandled

279
00:13:47.399 --> 00:13:50.919
<v Speaker 2>exception hitting the context like that will immediately tear down

280
00:13:50.960 --> 00:13:54.320
<v Speaker 2>the entire application domain. Your app just vanishes.

281
00:13:53.960 --> 00:13:57.320
<v Speaker 1>From the screen, which is terrifying, just poof gone. So

282
00:13:57.360 --> 00:14:01.360
<v Speaker 1>the takeaway is absolute avoid ACYNC void all costs, unless

283
00:14:01.399 --> 00:14:05.039
<v Speaker 1>you are strictly wiring up a legacy UI event handler.

284
00:14:05.159 --> 00:14:05.679
<v Speaker 2>Absolutely.

285
00:14:06.279 --> 00:14:08.519
<v Speaker 1>So we've figured out how to wait for external systems

286
00:14:08.559 --> 00:14:10.679
<v Speaker 1>to respond without locking up our app and how to

287
00:14:10.720 --> 00:14:14.000
<v Speaker 1>avoid crashing the context. But what happens when the delay

288
00:14:14.200 --> 00:14:17.279
<v Speaker 1>isn't external? What happens when our own CPU is the

289
00:14:17.279 --> 00:14:20.440
<v Speaker 1>bottleneck because we have a massive internal data set to crunch.

290
00:14:20.639 --> 00:14:24.919
<v Speaker 2>Ah okay, this is where we pivot from a secretous programming,

291
00:14:24.960 --> 00:14:29.840
<v Speaker 2>which remember, is about ioefficiency, to parallel programming, which is

292
00:14:29.840 --> 00:14:34.039
<v Speaker 2>about CPU throughput. Parallel programming is about taking a massive

293
00:14:34.039 --> 00:14:38.039
<v Speaker 2>amount of computational work and partitioning it across multiple cores

294
00:14:38.320 --> 00:14:39.279
<v Speaker 2>to get it done faster.

295
00:14:39.639 --> 00:14:42.440
<v Speaker 1>Let's ground this in a modern scenario. Instead of just

296
00:14:42.600 --> 00:14:46.960
<v Speaker 1>doing work, imagine we have a massive database query that

297
00:14:47.039 --> 00:14:50.039
<v Speaker 1>takes an hour to run on a single core. We

298
00:14:50.039 --> 00:14:52.039
<v Speaker 1>don't want to just yield the thread and wait an hour.

299
00:14:52.279 --> 00:14:55.200
<v Speaker 1>We want to partition that data set, effectively sharding it

300
00:14:55.240 --> 00:14:58.320
<v Speaker 1>into ten smaller chunks, and hand each chunk to a

301
00:14:58.320 --> 00:15:00.720
<v Speaker 1>different CPU core to PROCESSI simultaneously.

302
00:15:00.840 --> 00:15:03.799
<v Speaker 2>Yeah, that's a perfect architectural analogy. It's like a chef, right.

303
00:15:04.159 --> 00:15:06.320
<v Speaker 2>Acinc is like a chef putting a pianney evident wiping

304
00:15:06.320 --> 00:15:08.519
<v Speaker 2>down the counters while it bakes. Like that, The parallel

305
00:15:08.559 --> 00:15:11.440
<v Speaker 2>processing is like hiring ten sous chefs to chop ten

306
00:15:11.480 --> 00:15:13.000
<v Speaker 2>thousand carrots at the exact same time.

307
00:15:13.120 --> 00:15:13.840
<v Speaker 1>Wow. Yeah.

308
00:15:13.879 --> 00:15:16.840
<v Speaker 2>And the task parallel library gives us two main tools

309
00:15:16.840 --> 00:15:20.759
<v Speaker 2>for this kind of data parallelism, parallel four and pliq

310
00:15:21.279 --> 00:15:24.960
<v Speaker 2>which stands for parallel linq. Both of these tools handle

311
00:15:25.000 --> 00:15:28.399
<v Speaker 2>a complex logic of partitioning your data set and destructing

312
00:15:28.519 --> 00:15:30.879
<v Speaker 2>the chunks to different threads automatically, but.

313
00:15:30.960 --> 00:15:35.159
<v Speaker 1>The sources highlight a critical behavioral difference between how plionq

314
00:15:35.399 --> 00:15:38.639
<v Speaker 1>and the parallel class actually interact with the operating system.

315
00:15:39.159 --> 00:15:43.000
<v Speaker 2>Yes, and it really comes down to aggressiveness. Pli inq

316
00:15:43.320 --> 00:15:47.480
<v Speaker 2>is incredibly elegant to write, but by default it is greedy.

317
00:15:47.759 --> 00:15:50.399
<v Speaker 2>Greedy how it assumes your query is the most important

318
00:15:50.399 --> 00:15:53.159
<v Speaker 2>thing happening on the entire machine, and it will eagerly

319
00:15:53.159 --> 00:15:55.440
<v Speaker 2>try to spin up enough threads to consume every single

320
00:15:55.480 --> 00:15:57.240
<v Speaker 2>CPU core available, which.

321
00:15:57.080 --> 00:15:58.919
<v Speaker 1>Sounds great for speed, but what if this is running

322
00:15:58.919 --> 00:16:00.519
<v Speaker 1>on a web server that is all so trying to

323
00:16:00.559 --> 00:16:03.080
<v Speaker 1>serve hundreds of other user requests at the same time.

324
00:16:03.399 --> 00:16:07.799
<v Speaker 2>Then plimq can actually degrade your overall performance by starving

325
00:16:07.879 --> 00:16:10.759
<v Speaker 2>the OS of threads. It just hogs everything. That is

326
00:16:10.759 --> 00:16:13.039
<v Speaker 2>where parallel for each is often the safer choice. The

327
00:16:13.080 --> 00:16:15.840
<v Speaker 2>parallel class methods are designed to be polite. Yeah. They

328
00:16:15.919 --> 00:16:19.600
<v Speaker 2>dynamically monitor the current CPU load and adjust their thread

329
00:16:19.679 --> 00:16:23.960
<v Speaker 2>usage if the server gets busy with other requests, parallel

330
00:16:24.600 --> 00:16:27.399
<v Speaker 2>for each will actually throttle itself back to play nicely

331
00:16:27.399 --> 00:16:28.600
<v Speaker 2>with the rest of the ecosystem.

332
00:16:28.919 --> 00:16:31.559
<v Speaker 1>But regardless of which tool you use, there is a

333
00:16:31.600 --> 00:16:35.159
<v Speaker 1>golden rule that dictates whether a parallelization will actually speed

334
00:16:35.279 --> 00:16:37.399
<v Speaker 1>up your code or just grind it to a halt,

335
00:16:37.840 --> 00:16:40.799
<v Speaker 1>and that rule is the chunks of work must be

336
00:16:40.879 --> 00:16:42.159
<v Speaker 1>completely independent.

337
00:16:42.840 --> 00:16:45.240
<v Speaker 2>This goes right back to the functional mindset we discussed

338
00:16:45.279 --> 00:16:48.320
<v Speaker 2>at the start. If you shard your data set across

339
00:16:48.399 --> 00:16:51.600
<v Speaker 2>eight cores, but all eight threads are trying to write

340
00:16:51.639 --> 00:16:54.720
<v Speaker 2>their results into a single shared list in memory, you

341
00:16:54.840 --> 00:16:57.320
<v Speaker 2>have to introduce locks to prevent data corruption.

342
00:16:57.840 --> 00:17:00.240
<v Speaker 1>And what happens when eight threads hit a lot at

343
00:17:00.240 --> 00:17:01.600
<v Speaker 1>the exact same time you.

344
00:17:01.559 --> 00:17:04.519
<v Speaker 2>Get locked contention, Seven of those threads instantly freeze and

345
00:17:04.599 --> 00:17:06.359
<v Speaker 2>just stand in line waiting for the first thread to

346
00:17:06.359 --> 00:17:09.839
<v Speaker 2>finish writing. You've just destroyed your parallel efficiency. In fact,

347
00:17:10.079 --> 00:17:12.880
<v Speaker 2>because of the overhead of managing those locks and switching

348
00:17:12.920 --> 00:17:16.680
<v Speaker 2>thread contexts, your code will likely run significantly slower than

349
00:17:16.720 --> 00:17:18.160
<v Speaker 2>if you had just used a single thread in the

350
00:17:18.160 --> 00:17:22.599
<v Speaker 2>first place. Independence is the absolute key to scalability here.

351
00:17:22.599 --> 00:17:25.359
<v Speaker 1>Which perfectly sets up the final evolution the book covers.

352
00:17:25.920 --> 00:17:29.839
<v Speaker 1>We've solved waiting for external IO, we've solved maximizing the

353
00:17:29.839 --> 00:17:33.920
<v Speaker 1>CPU for static data sets, but modern applications rarely deal

354
00:17:34.119 --> 00:17:37.400
<v Speaker 1>entirely in static data. What happens when you aren't iterating

355
00:17:37.440 --> 00:17:39.759
<v Speaker 1>over a fixed array, but instead you are dealing with

356
00:17:39.799 --> 00:17:42.680
<v Speaker 1>an infinite stream of events over time or building a

357
00:17:42.720 --> 00:17:45.920
<v Speaker 1>complex multi step processing pipeline that takes.

358
00:17:45.720 --> 00:17:49.559
<v Speaker 2>Us into reactive extensions generally known as RX and TPL dataflow.

359
00:17:50.160 --> 00:17:53.680
<v Speaker 2>Let's start with RX. If you understand LIANQ, you can

360
00:17:53.759 --> 00:17:58.680
<v Speaker 2>understand RX as basically LANQ two events normal IONQ uses

361
00:17:58.680 --> 00:18:01.799
<v Speaker 2>a pole model static array at memory, and your code

362
00:18:01.839 --> 00:18:04.240
<v Speaker 2>proactively pulls the data out one item at a time.

363
00:18:04.319 --> 00:18:07.000
<v Speaker 1>But RX flips the architecture to a push model.

364
00:18:07.079 --> 00:18:09.799
<v Speaker 2>Exactly With RX, the data might not even exist yet,

365
00:18:09.960 --> 00:18:12.039
<v Speaker 2>and said you define a query and as events happen

366
00:18:12.079 --> 00:18:14.839
<v Speaker 2>in real time, they push themselves through your query logic.

367
00:18:15.039 --> 00:18:17.880
<v Speaker 1>The source material makes a really vital distinction here for

368
00:18:17.960 --> 00:18:22.880
<v Speaker 1>anyone architecting event driven systems. The difference between hot and

369
00:18:22.960 --> 00:18:25.839
<v Speaker 1>cold observables. I found this super helpful.

370
00:18:25.920 --> 00:18:28.559
<v Speaker 2>It's a crucial concept. Yeah. A hot observable is an

371
00:18:28.559 --> 00:18:32.319
<v Speaker 2>event stream that is broadcasting regardless of whether anyone is listening.

372
00:18:32.799 --> 00:18:35.480
<v Speaker 2>Think of mouse movements across a screen or a live

373
00:18:35.519 --> 00:18:39.119
<v Speaker 2>feed of stock market ticks. If your application isn't actively

374
00:18:39.160 --> 00:18:42.200
<v Speaker 2>subscribed to that stream, those events are just gone. They

375
00:18:42.279 --> 00:18:43.519
<v Speaker 2>pass right into the ether.

376
00:18:43.920 --> 00:18:46.680
<v Speaker 1>Contrast that with a cold observable. The book uses an

377
00:18:46.759 --> 00:18:49.799
<v Speaker 1>HTTP download as an example. The server has the data,

378
00:18:49.880 --> 00:18:53.119
<v Speaker 1>but the download stream doesn't actually begin broadcasting until your

379
00:18:53.160 --> 00:18:56.559
<v Speaker 1>code explicitly subscribes to it. It waits for you, right, and.

380
00:18:56.680 --> 00:18:59.759
<v Speaker 2>RX gives developers a unified interface to treat both of

381
00:18:59.799 --> 00:19:02.440
<v Speaker 2>these chaotic streams of events as if they were just

382
00:19:02.519 --> 00:19:04.000
<v Speaker 2>standard collections of data.

383
00:19:04.039 --> 00:19:06.359
<v Speaker 1>And then we have TPL data flow, which tackles a

384
00:19:06.359 --> 00:19:10.160
<v Speaker 1>slightly different architectural problem. Where RX is great for reacting

385
00:19:10.200 --> 00:19:13.240
<v Speaker 1>to events, data flow is designed for building heavy processing

386
00:19:13.279 --> 00:19:14.480
<v Speaker 1>pipelines or meshes.

387
00:19:14.839 --> 00:19:18.079
<v Speaker 2>Yeah. It uses an actor bottle approach. You create distinct

388
00:19:18.160 --> 00:19:22.000
<v Speaker 2>blocks of logic, like say, a transform block to parse

389
00:19:22.039 --> 00:19:24.519
<v Speaker 2>incoming data and an action block to save it to

390
00:19:24.519 --> 00:19:25.160
<v Speaker 2>a database.

391
00:19:25.240 --> 00:19:27.839
<v Speaker 1>You link these blocks together pour data into the top

392
00:19:27.880 --> 00:19:30.880
<v Speaker 1>of the funnel, and the run time automatically handles passing

393
00:19:30.920 --> 00:19:33.200
<v Speaker 1>the data from one block to the next. But as

394
00:19:33.240 --> 00:19:36.160
<v Speaker 1>I was reading this section, I noticed a massive red

395
00:19:36.200 --> 00:19:38.039
<v Speaker 1>flag regarding memory management.

396
00:19:38.160 --> 00:19:39.720
<v Speaker 2>Oh, the unbounded buffers.

397
00:19:39.839 --> 00:19:43.119
<v Speaker 1>Yes, if I have a fast source block downloading thousands

398
00:19:43.160 --> 00:19:45.839
<v Speaker 1>of files and it's pushing them into a slow target

399
00:19:45.880 --> 00:19:49.079
<v Speaker 1>block that has to parse them, won't the slow block

400
00:19:49.200 --> 00:19:50.720
<v Speaker 1>just get completely overwhelmed?

401
00:19:50.920 --> 00:19:55.240
<v Speaker 2>You've identified the exact danger of unbounded buffers. By default,

402
00:19:55.319 --> 00:19:58.039
<v Speaker 2>the input buffer on a data flow block is unbounded,

403
00:19:58.400 --> 00:20:01.319
<v Speaker 2>meaning it will blindly accep every single piece of data

404
00:20:01.359 --> 00:20:03.799
<v Speaker 2>pushed into it, hoarding it all in memory. Right. If

405
00:20:03.839 --> 00:20:06.640
<v Speaker 2>your parsing block is slower than your download block, that

406
00:20:06.720 --> 00:20:09.400
<v Speaker 2>unbounded buffer will just grow and grow until your application

407
00:20:09.480 --> 00:20:11.079
<v Speaker 2>crashes with an out of memory exception.

408
00:20:11.440 --> 00:20:13.519
<v Speaker 1>So how do we implement back pressure? Like, how do

409
00:20:13.599 --> 00:20:15.680
<v Speaker 1>we tell the fast block to just slow down?

410
00:20:15.920 --> 00:20:19.920
<v Speaker 2>The solution is actually a simple configuration property called bounded capacity.

411
00:20:20.640 --> 00:20:23.640
<v Speaker 2>By setting a strict limit on the buffer, say capping

412
00:20:23.640 --> 00:20:26.359
<v Speaker 2>it at ten items, you force the block to reject

413
00:20:26.359 --> 00:20:29.400
<v Speaker 2>new data once it's full. Oh nice, Yeah, this creates

414
00:20:29.480 --> 00:20:33.039
<v Speaker 2>natural back pressure. The system pauses the fast download block

415
00:20:33.160 --> 00:20:35.920
<v Speaker 2>until the slow parsing block has room to accept more data.

416
00:20:36.599 --> 00:20:39.960
<v Speaker 2>It turns a fragile memory hogging script into a robust

417
00:20:40.079 --> 00:20:41.400
<v Speaker 2>self throttling pipeline.

418
00:20:41.440 --> 00:20:44.640
<v Speaker 1>It creates load balancing right out of thin air, which

419
00:20:45.039 --> 00:20:48.160
<v Speaker 1>really brings us full circle. We started this deep dive

420
00:20:48.279 --> 00:20:51.559
<v Speaker 1>looking at the civic ca you know, the chaotic, unpredictable

421
00:20:51.640 --> 00:20:54.319
<v Speaker 1>world of legacy threading that crashed applications and left a

422
00:20:54.319 --> 00:20:56.759
<v Speaker 1>bad taste in our mouths. And step by step we've

423
00:20:56.759 --> 00:20:59.000
<v Speaker 1>seen how c sharp has evolved to solve these problems.

424
00:20:59.079 --> 00:21:02.359
<v Speaker 2>Yeah, we move from men thread allocation to the dynamic threadpool.

425
00:21:02.440 --> 00:21:05.039
<v Speaker 1>We explored the magic of the compiler state machine, letting

426
00:21:05.119 --> 00:21:08.720
<v Speaker 1>us await operations without blocking threads while using configure weight

427
00:21:08.920 --> 00:21:11.680
<v Speaker 1>false to protect the UI message pump and of course

428
00:21:11.720 --> 00:21:14.799
<v Speaker 1>avoiding async void to prevent catastrophic crashes.

429
00:21:15.119 --> 00:21:18.720
<v Speaker 2>We learned how to shatter CPU bottlenecks by partitioning data

430
00:21:18.759 --> 00:21:21.960
<v Speaker 2>with parallel for each and adhering strictly to the rule

431
00:21:22.000 --> 00:21:24.079
<v Speaker 2>of independence to avoid lock contention.

432
00:21:24.880 --> 00:21:27.960
<v Speaker 1>And finally, we looked at how to handle infinite streams

433
00:21:28.000 --> 00:21:31.279
<v Speaker 1>of time based data with RX and how to build

434
00:21:31.400 --> 00:21:35.440
<v Speaker 1>robust self throttling pipelines with TPL data flow.

435
00:21:35.480 --> 00:21:38.759
<v Speaker 2>Really is a profound architectural evolution. I want to leave

436
00:21:38.799 --> 00:21:41.039
<v Speaker 2>you with this thought, based on everything we've uncovered today,

437
00:21:41.480 --> 00:21:44.480
<v Speaker 2>look at the trajectory here. We've moved entirely away from

438
00:21:44.480 --> 00:21:50.240
<v Speaker 2>writing rigid, sequential code that force fully micromanages memory and threads. Instead,

439
00:21:50.319 --> 00:21:53.880
<v Speaker 2>we are utilizing pure functions, immutable data state machines, and

440
00:21:53.920 --> 00:21:57.440
<v Speaker 2>reactive pipelines. If our applications are becoming networks of independent,

441
00:21:57.519 --> 00:22:00.839
<v Speaker 2>self throttling blocks reacting to streams of data, are we

442
00:22:00.880 --> 00:22:04.680
<v Speaker 2>even writing instructions anymore? Or are we truly choreographing a

443
00:22:04.720 --> 00:22:08.920
<v Speaker 2>self balancing digital ecosystem. Think about how adopting that specific

444
00:22:09.000 --> 00:22:12.160
<v Speaker 2>mindset could completely change the resilience and scalability of your

445
00:22:12.160 --> 00:22:13.039
<v Speaker 2>next big project.

446
00:22:13.200 --> 00:22:16.039
<v Speaker 1>Because when you stop fighting the system for control and

447
00:22:16.119 --> 00:22:20.160
<v Speaker 1>start choreographing the ecosystem, you ensure your code never becomes

448
00:22:20.160 --> 00:22:22.799
<v Speaker 1>a legacy pest. Thank you for joining us on this

449
00:22:22.880 --> 00:22:25.720
<v Speaker 1>deep dive into the source material. We will see you

450
00:22:25.720 --> 00:22:26.200
<v Speaker 1>next time.
