Skip to content

Commit fb9f61a

Browse files
committed
main
1 parent aada76d commit fb9f61a

File tree

7 files changed

+214
-23
lines changed

7 files changed

+214
-23
lines changed

docs/document/Modern CSharp/docs/Parallel Programming/Concurrent Collections.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,11 @@ ConcurrentDictionary<string, string> dict = [];
2020
string? val;
2121

2222
// if key is registered, update with the transformed value
23-
// if key is not registered, callback wouldn't be called
23+
// if key is not registered, callback wouldn't be called and newValue is added for key
2424
val = dict.AddOrUpdate("key", "newValue", (key, old) => $"transformed {old} for {key}");
25-
// add with transformed key
25+
// add with transformed key as value
2626
// or
27-
// update with transformed with key and old value.
27+
// update with transformed with key and old value as new value
2828
val = dict.AddOrUpdate(
2929
"key",
3030
key => $"transformed from {key} on add",
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
# Thread Coordination Primitives
2+
3+
## Barrier
4+
5+
`Barrier` allows multiple threads to execute in same phase and not moving to next phase until all participant signaled.
6+
7+
- `Barrier` must have a specified count of participant threads
8+
- `barrier.SignalAndWait()` increases the inner counter in `Barrier`
9+
- phase is completed when the number of threads signaled reaches **the count** of barrier
10+
- new phase is started when any thread signaled made the counter increased from 0 to 1
11+
- each thread would not continue until sufficient count of threads have signaled(participated)
12+
13+
```cs
14+
// you may register a event on phase finished // [!code highlight]
15+
Barrier barrier = new(3, b => { // [!code highlight]
16+
Console.WriteLine($"phase {b.CurrentPhaseNumber} has finished"); // [!code highlight]
17+
}); // [!code highlight]
18+
19+
var tasks = Enumerable.Range(1, 3).Select(_ => {
20+
return Task.Run(() => {
21+
// notifying barrier that this thread has entered
22+
// the counter inside increments
23+
// and all participants would continue when the count hits limit
24+
// and the count would reset to the sepcified
25+
barrier.SignalAndWait(); // signal for the first time to enter phase 0 // [!code highlight]
26+
Console.WriteLine($"Task {Task.CurrentId} is handling phase {barrier.CurrentPhaseNumber}");
27+
barrier.SignalAndWait(); // signal again indicating to enter phase 1
28+
});
29+
});
30+
31+
Task.WaitAll(tasks);
32+
33+
// phase 0 has finished
34+
// Task 1 is handling phase 1
35+
// Task 3 is handling phase 1
36+
// Task 2 is handling phase 1
37+
// phase 1 has finished
38+
```
39+
40+
## CountDownEvent
41+
42+
`CountDownEvent` is a primitive to block certain threads until a count down has reached
43+
44+
```cs
45+
CountdownEvent @event = new(3);
46+
47+
var waiting = Task.Run(() => {
48+
Console.WriteLine("This task is waiting for count down completion");
49+
@event.Wait(); // blocking until count down finished // [!code highlight]
50+
Console.WriteLine("Task finished after count down");
51+
});
52+
53+
54+
var countdown = Enumerable.Range(1, 3).Select(_ => {
55+
return Task.Run(() => {
56+
Thread.Sleep(1000);
57+
Console.WriteLine("count down");
58+
@event.Signal();
59+
});
60+
});
61+
62+
Task.WaitAll([waiting, .. countdown]);
63+
64+
// This task is waiting for count down finished
65+
// count down
66+
// count down
67+
// count down
68+
// Task finished after count down
69+
```
70+
71+
> [!NOTE]
72+
> You may increase a count dynamically using `AddCount` or `TryAddCount`
73+
74+
## ManualResetEventSlim
75+
76+
`ManualResetEventSlim` behaves somewhat like a one-time counter that counts up to 1, it can be used to implement a continuation.
77+
78+
```cs
79+
// optionally set initialState impling whther the event is already signaled
80+
ManualResetEventSlim @event = new(initialState: false);
81+
82+
var waiting = Task.Run(() => {
83+
Console.WriteLine("waiting for one time signal");
84+
@event.Wait(); // [!code highlight]
85+
Console.WriteLine("post action triggered");
86+
@event.Wait();
87+
if (@event.Wait(1000)) { }
88+
Console.WriteLine("still reachable since the event has to be reset manually to regain blocking"); // [!code highlight]
89+
// reset the count to regain blocking
90+
@event.Reset(); // [!code warning]
91+
@event.Wait(); // infinite blocking since now no signal would be sent again // [!code warning]
92+
});
93+
94+
var count = Task.Run(() => {
95+
Console.WriteLine("perform something and then signal");
96+
@event.Set();
97+
});
98+
99+
waiting.Wait();
100+
```
101+
102+
> [!NOTE]
103+
> Use `AutoResetEvent` if auto reset on count is required which automatically reset state when wait succeeded
104+
>```cs
105+
>AutoResetEvent @event = new(initialState: false);
106+
>
107+
>var waiting = Task.Run(() => {
108+
> Console.WriteLine("waiting for one time signal");
109+
> _ = @event.WaitOne(); // [!code highlight]
110+
> Console.WriteLine("post action triggered");
111+
> _ = @event.WaitOne(); // would hang here forever since state was reset automatically // [!code warning]
112+
> Console.WriteLine("not reachable!!"); // [!code warning]
113+
>});
114+
>
115+
>var countdown = Task.Run(() => {
116+
> Console.WriteLine("perform something and then signal");
117+
> _ = @event.Set();
118+
>});
119+
>
120+
>waiting.Wait();
121+
>```
122+
123+
## SemaphoreSlim
124+
125+
`SemaphoreSlim` allows limited count of multiple threads to execute concurrently and release the hold dynamically.
126+
127+
- `semaphore.Wait(...)`: would block current thread when count is 0, so you should release to increase the count somewhere to make sure it continues
128+
- `semaphore.Release(int?)`: to release specific count so other threads could continue
129+
- returns a previous count of semaphore
130+
- `maxCount` on constructor came into play since release is a manual control.
131+
- when `maxCount` was reached, `SemaphoreFullException` would be thrown.
132+
133+
So the *count* is essentially a limit of how many threads could continue for now, if the count reaches 0, all other threads not entered semaphore have to wait.
134+
Remember to *release* to get the count back so other thread could enter semaphore later.
135+
136+
```cs
137+
using System.Diagnostics;
138+
using System.Runtime.ConstrainedExecution;
139+
140+
// three in a row, can dynamically increase the count to 10 at most
141+
SemaphoreSlim semaphore = new(initialCount: 3, maxCount: 10);
142+
143+
int shared = 0;
144+
145+
var tasks = Enumerable.Range(1, 100).Select(n => {
146+
return Task.Run(() => {
147+
148+
semaphore.Wait(); // would block when count is 0 // [!code highlight]
149+
150+
Thread.Sleep(1000);
151+
152+
Console.WriteLine(n); // order is not guaranteed by semaphore
153+
Interlocked.Add(ref shared, 1); // multiple thread would still come in so protection is neeeded
154+
155+
_ = semaphore.Release(); // [!code highlight]
156+
});
157+
});
158+
159+
Task.WaitAll(tasks);
160+
161+
Debug.Assert(shared is 100);
162+
```

docs/document/Modern CSharp/docs/Parallel Programming/Task/2.Wait for Task.md

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@ Waiting a task means the execution of code is synchronous but there's a essentia
2626
- `Task.Wait*`: utils to wait a batch of tasks in a **blocking** manner.
2727
- `Task.When*`: utils to wait a batch of tasks in a **non-blocking** manner.
2828

29-
3029
A task must be started before awaiting, or the thread would be blocked forever.
3130
A task from `async` method is started implicitly so no worry here.
3231

@@ -67,6 +66,17 @@ Task<int> first = await Task.WhenAny(tasks);
6766
Console.WriteLine(first.Result); // should be 1
6867
```
6968

69+
> [!IMPORTANT]
70+
> `WhenAll` and `WaitAll` would not throw any exception from tasks until all tasks were finished.
71+
> So if any task hanged for any reason such as deadlock, the wait would continue forever.
72+
>```cs
73+
>var exception = Task.FromException(new Exception());
74+
>var infinite = Task.Delay(Timeout.Infinite); // a task never finishes
75+
>
76+
>Task.WaitAll(exception, infinite); // blocks forever, would never throw // [!code warning]
77+
>// await Task.WhenAny(exception, infinite);
78+
>```
79+
7080
### Practical Usage
7181
7282
`WhenAny` can be used as a simple countdown with `Task.Delay`

docs/document/Modern CSharp/docs/Parallel Programming/Task/3.Task Cancellation.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ Task task = Task.Factory.StartNew(() => {
7373
token.ThrowIfCancellationRequested();
7474
Thread.Sleep(1000);
7575
}
76-
}, cts.Token); // it's oke, all tokens from same source are identical
76+
}, cts.Token); // it's ok, all tokens from same source are identical
7777
7878
try {
7979
task.Wait();

docs/document/Modern CSharp/docs/Parallel Programming/Task/Task Status.md

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -62,13 +62,12 @@ try {
6262
Faulted happens on one of the scenarios:
6363

6464
- Any exception besides `OperationCanceledException` was thrown.
65-
- `OperationCanceledException` is thrown && (`token.IsCancellationRequested` is false || `token` in closure passed to `OperationCanceledException` != `token` as parameter)
65+
- `OperationCanceledException` is thrown but cancellation failed.
6666
- Any exception is not `OperationCanceledException` was thrown on task creation. A wait on the task created anyway results Faulted.
6767

6868

6969
```cs
70-
Task task = Task.Factory.StartNew(async () => {
71-
await Task.Delay(100);
70+
Task task = Task.Factory.StartNew(() => {
7271
throw new Exception(); // not an OperationCanceledException // [!code highlight]
7372
});
7473

docs/document/Modern CSharp/docs/Understanding MSBuild.md

Lines changed: 34 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,20 @@ Item attributes are for controlling how items could be initialized, added and re
9999
- use `KeepMetadata` or `RemoveMetadata` to optionally include or exclude metadata from when **creating items by transforming** from another **within a `<Target>`**
100100
```xml
101101
<ItemGroup>
102-
<Old Include="*"> <!-- [!code highlight] -->
103-
<Foo>foo</Foo> <!-- [!code highlight] -->
104-
<Bar>bar</Bar> <!-- [!code highlight] -->
105-
</Old> <!-- [!code highlight] -->
102+
<Old Include="*">
103+
<Foo>foo</Foo>
104+
<Bar>bar</Bar>
105+
</Old>
106106
</ItemGroup>
107107

108108
<Target>
109109
<ItemGroup>
110-
<New Include="@(Old)" RemoveMetadata="Foo"/> <!-- transform from Old --> <!-- [!code highlight] -->
110+
<New Include="@(Old)" RemoveMetadata="Foo"/> <!-- transform from Old -->
111111
</ItemGroup>
112112
<!-- Old.Foo was removed after transformation -->
113-
<Message Text="Old.Foo was removed after transformation" <!-- [!code highlight] -->
114-
Condition="%(New.Foo) == ''" <!-- [!code highlight] -->
115-
Importance="high"/> <!-- [!code highlight] -->
113+
<Message Text="Old.Foo was removed after transformation"
114+
Condition="%(New.Foo) == ''"
115+
Importance="high"/>
116116
</Target>
117117
```
118118
- use `KeepDuplicates` when adding new item within a `<Target>` that you expect the new would be added when deplicates exist.
@@ -127,10 +127,10 @@ Item attributes are for controlling how items could be initialized, added and re
127127
<Target Name="Hello">
128128
<ItemGroup>
129129
<!-- bar would not be added since it already exists in FooList -->
130-
<FooList Include="bar" KeepDuplicates="false" /> <!-- [!code highlight] -->
130+
<FooList Include="bar" KeepDuplicates="false" />
131131
</ItemGroup>
132132
<!-- foo;bar;foo;qux -->
133-
<Message Text="@(FooList)" Importance="high"></Message> <!-- [!code highlight] -->
133+
<Message Text="@(FooList)" Importance="high"></Message>
134134
</Target>
135135
```
136136
- `Exclude`: exclude items on declaration
@@ -158,9 +158,9 @@ Item attributes are for controlling how items could be initialized, added and re
158158
<Target Name="Hello">
159159
<!-- Proj items are to be matched by metadata FileName -->
160160
<ItemGroup>
161-
<CSFile Remove="@(Proj)" <!-- [!code highlight] -->
162-
MatchOnMetadata="FileName" <!-- [!code highlight] -->
163-
MatchOnMetadataOptions="CaseSensitive" /> <!-- [!code highlight] -->
161+
<CSFile Remove="@(Proj)"
162+
MatchOnMetadata="FileName"
163+
MatchOnMetadataOptions="CaseSensitive" />
164164
</ItemGroup>
165165
<!-- Remained cs items: Programs.cs -->
166166
<Message Text="Remained cs items: %(CSFile.Identity)" Importance="high"></Message>
@@ -174,7 +174,7 @@ Item attributes are for controlling how items could be initialized, added and re
174174
<FooMetaData>this is a foo metadata</FooMetaData>
175175
</FooList>
176176
<!-- update FooMetaData for foo and bar -->
177-
<FooList Update="foo;bar" FooMetaData="this is a bar metadata now!"/> <!-- [!code highlight] -->
177+
<FooList Update="foo;bar" FooMetaData="this is a bar metadata now!"/>
178178
</ItemGroup>
179179

180180
<Target Name="Hello">
@@ -201,7 +201,7 @@ There's some intrinsic functions to be used to **transform** a item list to anot
201201
></ItemGroup>
202202
>
203203
><Target Name="Hello">
204-
> <Message Text="%(FooList.Identity) @(FooList->Count())" Importance="high" /> <!-- [!code highlight] -->
204+
> <Message Text="%(FooList.Identity) @(FooList->Count())" Importance="high" />
205205
> <!-- foo 2
206206
> bar 1
207207
> qux 1 -->
@@ -225,6 +225,25 @@ Each item has pre-defined metadata can be accessed.
225225
> [!NOTE]
226226
> If a item does not represent a file, the most of intrinsic metadata would be empty.
227227

228+
#### Metadata Mutation
229+
230+
Metadata should be mutated by batching using a `Condition` within a `<Target>`
231+
232+
```xml
233+
<ItemGroup>
234+
<Foo Include="2" Bar="foo" />
235+
<Foo Include="1" Bar="bar" />
236+
</ItemGroup>
237+
238+
<Target Name="Foo">
239+
<ItemGroup>
240+
<Foo Condition=" '%(Bar)' == 'blue' ">
241+
<Bar>baz</Bar>
242+
</Foo>
243+
</ItemGroup>
244+
</Target>
245+
```
246+
228247
### Common Items
229248

230249
- `Reference`: reference to dll files

docs/document/Modern CSharp/docs/Understanding String Formatting.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,7 @@ Enum formatting is handled by `Enum.ToString` static methods. They're implicitly
100100

101101
There's two scenarios of enum formatting
102102
- singular value
103+
103104
A valid enum value as integer can be directly evaluated as the enum name.
104105
More specifically, the format is **G**eneral when there's no format specified.
105106
If an integer is not a valid value for the enum, compiler does not yell but the default evaluation for it will be the number itself

0 commit comments

Comments
 (0)