Performance in
.NET Core
Gdańsk, 2019.11.21
2
Number of requests on production
Numberofrequests
3
Measurement tools
JetBrains dotTrace
JetBrains dotMemory
4
Size of objects
Namespace Allocated bytes
Allocated
objects
Collected bytes
Collected
objects
Newtonsoft
JSON
162 368 6 803 105 168 5 851
JSON.NET 68 276 2 122 39 564 1 750
5
The most allocated types
2.2
3.0
6
Allocated strings by Newtonsoft
7
Time of processing requests (smaller is better)
436
684
775 792
172
1768
282
353
581
677 720
160
1463
207
0
200
400
600
800
1000
1200
1400
1600
1800
2000
Time[ms]
ASP.NET Core 2.2 ASP .NET Core 3.0
8
What’s the gain?
ASP.NET Core 2.2
4 909 ms
ASP.NET Core 3.0
4 161 ms
748 ms
-
=
Pipes
10
Hypothetical Use Case
Company needs to report all sale
transactions quaterly for statistics purposes.
11
Hypothetical Use Case
Company needs to report all sale
transactions quaterly for statistics purposes.
Since the Company makes thousands of
transactions every day - report file is very
large.
12
Hypothetical Use Case
Company needs to report all sale
transactions quaterly for statistics purposes.
Since the Company makes thousands of
transactions every day - report file is very
large.
Let’s assume the report is a CSV file.
13
Hypothetical Use Case
Company needs to report all sale
transactions quaterly for statistics purposes.
Since the Company makes thousands of
transactions every day - report file is very
large.
Let’s assume the report is a CSV file.
14
Naïve approach 1
app.Run(context => ProcessRequestAsync(context.Request.Body));
// NAIVE APPROACH
private async Task ProcessRequestAsync(Stream stream)
{
var buffer = new byte[1024];
while(true)
{
var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
return; // EOF
}
ProcessRowData(buffer, bytesRead);
}
}
15
Naïve approach 2
// BETTER APPROACH
private async Task ProcessRequestAsync(Stream stream)
{
var buffer = new byte[1024];
while (true)
{
var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length);
if (bytesRead == 0)
{
return; // EOF
}
var newLinePos = -1;
var bytesChecked = 0;
do
{
newLinePos = Array.IndexOf(buffer, (byte)'n', bytesChecked, bytesRead - bytesChecked);
if (newLinePos >= 0)
{
var lineLength = newLinePos - bytesChecked;
ProcessRowData(buffer, bytesChecked, lineLength);
}
bytesChecked += newLinePos + 1;
}
while (newLinePos > 0);
}
}
16
CSV might be tricky
Some product; 5.0; "Amazingn
Category"; 10n
Other product; 1.0; Boring category; 2n
17
CSV might be tricky
Some product; 5.0; "Amazingn
Category"; 10n
Other product; 1.0; Boring category; 2n
18
How we can improve our approach?
We can allocate bigger buffer when we face longer line or multiline record
19
How we can improve our approach?
We can allocate bigger buffer when we face longer line or multiline record
We can create own pool of buffers, new buffer would be created when the previous gets
filled up (i.e. in 80% or more).
20
System.IO.Pipelines to the rescue
The mentioned issues can easily be solved by using Pipe.
21
System.IO.Pipelines to the rescue
The mentioned issues can easily be solved by using Pipe.
Pipe​
PipeWriter PipeReader
22
Pipes
private async Task ProcessRequestAsync(Stream stream)
{
var pipe = new Pipe();
var readFileTask = ReadFileAsync(stream, pipe.Writer);
var processFileTask = ProcessFileAsync(pipe.Reader);
await Task.WhenAll(readFileTask, processFileTask);
}
23
PipeWriter
private async Task ReadFileAsync(Stream stream, PipeWriter pipeWriter)
{
while (true)
{
Memory<byte> memory = pipeWriter.GetMemory(BufferSize);
int bytesRead = await stream.ReadAsync(memory);
if (bytesRead == 0)
{
break;
}
pipeWriter.Advance(bytesRead);
// flush data to PipeReader
FlushResult flushResult = await pipeWriter.FlushAsync();
if (flushResult.IsCompleted)
{
break;
}
}
pipeWriter.Complete(); // we are done
}
24
PipeReader
private async Task ProcessFileAsync(PipeReader pipeReader)
{
while(true)
{
ReadResult result = await pipeReader.ReadAsync();
ReadOnlySequence<byte> buffer = result.Buffer;
SequencePosition? position = null;
do
{
position = buffer.PositionOf((byte)'n'); // find position of newline character, read multiline row…
if (position != null)
{
ProcessRowData(buffer.Slice(0, position.Value));
buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); // move to next line
}
}
while (position != null);
if (result.IsCompleted)
{
break;
}
pipeReader.AdvanceTo(buffer.Start, buffer.End); // let know the PipeReader how much bytes were consumed
}
pipeReader.Complete(); // we are done
}
25
Partial read
Pipe.Reader.ReadAsync() Some product; 5
Some product; 5 .PositionOf((byte)'n') null
Pipe.Reader.ReadAsync() Some product; 5.0; Some Categoryn
Some product; 5.0; Some Categoryn .PositionOf((byte)'n') SequencePosition
Pipe.Reader.AdvanceTo( SequencePosition +1)
Pipe.Reader.ReadAsync() Other product; 3.0;…
26
ReadOnlySequence<T>, SequencePosition
Pipe
ReadOnlySequence
EndStart
27
Backpressure & flow control
28
Backpressure & flow control
29
Backpressure & flow control
30
Backpressure & flow control
31
Backpressure & flow control
32
Backpressure & flow control
PauseWriterThreshold
33
Backpressure & flow control
PauseWriterThreshold
34
Backpressure & flow control
PauseWriterThreshold
35
Backpressure & flow control
PauseWriterThreshold
36
Backpressure & flow control
PauseWriterThreshold
ResumeWriterThreshold
37
Backpressure & flow control
PauseWriterThreshold
ResumeWriterThreshold
38
How is it relevant?
39
How is it relevant?
Pipes originally were introduced for internal use in Kestrel. Eventually, the’ve
evolved into a part of public API.
40
How is it relevant?
Pipes originally were introduced for internal use in Kestrel. Eventually, the’ve
evolved into a part of public API.
The body of request in ASP.NET Core 3.0 is exposed via BodyReader property of
the HttpContext. BodyReader is in fact PipeReader.
41
How is it relevant?
Pipes originally were introduced for internal use in Kestrel. Eventually, the’ve
evolved into a part of public API.
The body of request in ASP.NET Core 3.0 is exposed via BodyReader property of
the HttpContext. BodyReader is in fact PipeReader.
Response body can be written with BodyWriter (PipeWriter).
Span<T>
and Memory<T>
43
Span<T>
.NET Core 2.1
ReadOnlySpan<T>
C# 7.2
ref struct
Array Slicing
44
Span<T> - Example
N A M E @ E M A I L
45
Span<T> - Example
N A M E @ E M A I L
public ReadOnlySpan<char> GetName(ReadOnlySpan<char> email)
{
var @position = email.LastIndexOf(‘@’);
return @position == -1
? ReadOnlySpan<char>.Empty
: email.Slice(0, @position);
}
46
Span<T> - Two versions
Background vector Created by brgfx - www.freepik.com
47
Span<T> - Two versions
Method Job Mean StdDev
SpanIndexer_Get .NET 4.6 0.6054 ns 0.0007 ns
SpanIndexer_Get .NET Core 1.1 0.6047 ns 0.0008 ns
SpanIndexer_Get .NET Core 2.0 0.5333 ns 0.0006 ns
SpanIndexer_Set .NET 4.6 0.6059 ns 0.0007 ns
SpanIndexer_Set .NET Core 1.1 0.6042 ns 0.0002 ns
SpanIndexer_Set .NET Core 2.0 0.5205 ns 0.0003 ns
Source: Adam Sitnik - https://adamsitnik.com/Span/#using-span
48
Limitations of Span<T>
Heap
as generic type
argument
implement any
interfaces
No
parameter of async
method
49
Memory<T>
.NET Core 2.1
Asynchronous world
C# 7.2
ReadOnlyMemory<T>
On Heap
Wrapper of Span<T>
50
Span<T> and Memory<T> - Supported in
.NET Standard 1.0 1.1 1.2
.NET Core 1.0 1.0 1.0
.NET Framework 1 4.5 4.5 4.5.1
Mono 4.6 4.6 4.6
Xamarin.iOS 10.0 10.0 10.0
Xamarin.Mac 3.0 3.0 3.0
Xamarin.Android 7.0 7.0 7.0
Universal Windows
Platform
10.0 10.0 10.0
Unity 2018.1 2018.1 2018.1
Json.NET
52
Json.NET – High performance JSON serializer and deserializer
.NET Standard 1.6 2.0 2.1
.NET Core 1.0 2.0 3.0
.NET Framework 4.6.1 4.6.1 Won’t support
Mono 4.6 5.4 6.4
Xamarin.iOS 10.0 10.14 12.16
Xamarin.Mac 3.0 3.8 5.16
Xamarin.Android 7.0 8.0 10.0
Universal Windows
Platform
10.0.16299 10.0. 16299 TBD
Unity 2018.1 2018.1 TBD
await FlushAsync();
Questions();

Performance .NET Core - M. Terech, P. Janowski

  • 1.
  • 2.
    2 Number of requestson production Numberofrequests
  • 3.
  • 4.
    4 Size of objects NamespaceAllocated bytes Allocated objects Collected bytes Collected objects Newtonsoft JSON 162 368 6 803 105 168 5 851 JSON.NET 68 276 2 122 39 564 1 750
  • 5.
    5 The most allocatedtypes 2.2 3.0
  • 6.
  • 7.
    7 Time of processingrequests (smaller is better) 436 684 775 792 172 1768 282 353 581 677 720 160 1463 207 0 200 400 600 800 1000 1200 1400 1600 1800 2000 Time[ms] ASP.NET Core 2.2 ASP .NET Core 3.0
  • 8.
    8 What’s the gain? ASP.NETCore 2.2 4 909 ms ASP.NET Core 3.0 4 161 ms 748 ms - =
  • 9.
  • 10.
    10 Hypothetical Use Case Companyneeds to report all sale transactions quaterly for statistics purposes.
  • 11.
    11 Hypothetical Use Case Companyneeds to report all sale transactions quaterly for statistics purposes. Since the Company makes thousands of transactions every day - report file is very large.
  • 12.
    12 Hypothetical Use Case Companyneeds to report all sale transactions quaterly for statistics purposes. Since the Company makes thousands of transactions every day - report file is very large. Let’s assume the report is a CSV file.
  • 13.
    13 Hypothetical Use Case Companyneeds to report all sale transactions quaterly for statistics purposes. Since the Company makes thousands of transactions every day - report file is very large. Let’s assume the report is a CSV file.
  • 14.
    14 Naïve approach 1 app.Run(context=> ProcessRequestAsync(context.Request.Body)); // NAIVE APPROACH private async Task ProcessRequestAsync(Stream stream) { var buffer = new byte[1024]; while(true) { var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) { return; // EOF } ProcessRowData(buffer, bytesRead); } }
  • 15.
    15 Naïve approach 2 //BETTER APPROACH private async Task ProcessRequestAsync(Stream stream) { var buffer = new byte[1024]; while (true) { var bytesRead = await stream.ReadAsync(buffer, 0, buffer.Length); if (bytesRead == 0) { return; // EOF } var newLinePos = -1; var bytesChecked = 0; do { newLinePos = Array.IndexOf(buffer, (byte)'n', bytesChecked, bytesRead - bytesChecked); if (newLinePos >= 0) { var lineLength = newLinePos - bytesChecked; ProcessRowData(buffer, bytesChecked, lineLength); } bytesChecked += newLinePos + 1; } while (newLinePos > 0); } }
  • 16.
    16 CSV might betricky Some product; 5.0; "Amazingn Category"; 10n Other product; 1.0; Boring category; 2n
  • 17.
    17 CSV might betricky Some product; 5.0; "Amazingn Category"; 10n Other product; 1.0; Boring category; 2n
  • 18.
    18 How we canimprove our approach? We can allocate bigger buffer when we face longer line or multiline record
  • 19.
    19 How we canimprove our approach? We can allocate bigger buffer when we face longer line or multiline record We can create own pool of buffers, new buffer would be created when the previous gets filled up (i.e. in 80% or more).
  • 20.
    20 System.IO.Pipelines to therescue The mentioned issues can easily be solved by using Pipe.
  • 21.
    21 System.IO.Pipelines to therescue The mentioned issues can easily be solved by using Pipe. Pipe​ PipeWriter PipeReader
  • 22.
    22 Pipes private async TaskProcessRequestAsync(Stream stream) { var pipe = new Pipe(); var readFileTask = ReadFileAsync(stream, pipe.Writer); var processFileTask = ProcessFileAsync(pipe.Reader); await Task.WhenAll(readFileTask, processFileTask); }
  • 23.
    23 PipeWriter private async TaskReadFileAsync(Stream stream, PipeWriter pipeWriter) { while (true) { Memory<byte> memory = pipeWriter.GetMemory(BufferSize); int bytesRead = await stream.ReadAsync(memory); if (bytesRead == 0) { break; } pipeWriter.Advance(bytesRead); // flush data to PipeReader FlushResult flushResult = await pipeWriter.FlushAsync(); if (flushResult.IsCompleted) { break; } } pipeWriter.Complete(); // we are done }
  • 24.
    24 PipeReader private async TaskProcessFileAsync(PipeReader pipeReader) { while(true) { ReadResult result = await pipeReader.ReadAsync(); ReadOnlySequence<byte> buffer = result.Buffer; SequencePosition? position = null; do { position = buffer.PositionOf((byte)'n'); // find position of newline character, read multiline row… if (position != null) { ProcessRowData(buffer.Slice(0, position.Value)); buffer = buffer.Slice(buffer.GetPosition(1, position.Value)); // move to next line } } while (position != null); if (result.IsCompleted) { break; } pipeReader.AdvanceTo(buffer.Start, buffer.End); // let know the PipeReader how much bytes were consumed } pipeReader.Complete(); // we are done }
  • 25.
    25 Partial read Pipe.Reader.ReadAsync() Someproduct; 5 Some product; 5 .PositionOf((byte)'n') null Pipe.Reader.ReadAsync() Some product; 5.0; Some Categoryn Some product; 5.0; Some Categoryn .PositionOf((byte)'n') SequencePosition Pipe.Reader.AdvanceTo( SequencePosition +1) Pipe.Reader.ReadAsync() Other product; 3.0;…
  • 26.
  • 27.
  • 28.
  • 29.
  • 30.
  • 31.
  • 32.
    32 Backpressure & flowcontrol PauseWriterThreshold
  • 33.
    33 Backpressure & flowcontrol PauseWriterThreshold
  • 34.
    34 Backpressure & flowcontrol PauseWriterThreshold
  • 35.
    35 Backpressure & flowcontrol PauseWriterThreshold
  • 36.
    36 Backpressure & flowcontrol PauseWriterThreshold ResumeWriterThreshold
  • 37.
    37 Backpressure & flowcontrol PauseWriterThreshold ResumeWriterThreshold
  • 38.
    38 How is itrelevant?
  • 39.
    39 How is itrelevant? Pipes originally were introduced for internal use in Kestrel. Eventually, the’ve evolved into a part of public API.
  • 40.
    40 How is itrelevant? Pipes originally were introduced for internal use in Kestrel. Eventually, the’ve evolved into a part of public API. The body of request in ASP.NET Core 3.0 is exposed via BodyReader property of the HttpContext. BodyReader is in fact PipeReader.
  • 41.
    41 How is itrelevant? Pipes originally were introduced for internal use in Kestrel. Eventually, the’ve evolved into a part of public API. The body of request in ASP.NET Core 3.0 is exposed via BodyReader property of the HttpContext. BodyReader is in fact PipeReader. Response body can be written with BodyWriter (PipeWriter).
  • 42.
  • 43.
    43 Span<T> .NET Core 2.1 ReadOnlySpan<T> C#7.2 ref struct Array Slicing
  • 44.
    44 Span<T> - Example NA M E @ E M A I L
  • 45.
    45 Span<T> - Example NA M E @ E M A I L public ReadOnlySpan<char> GetName(ReadOnlySpan<char> email) { var @position = email.LastIndexOf(‘@’); return @position == -1 ? ReadOnlySpan<char>.Empty : email.Slice(0, @position); }
  • 46.
    46 Span<T> - Twoversions Background vector Created by brgfx - www.freepik.com
  • 47.
    47 Span<T> - Twoversions Method Job Mean StdDev SpanIndexer_Get .NET 4.6 0.6054 ns 0.0007 ns SpanIndexer_Get .NET Core 1.1 0.6047 ns 0.0008 ns SpanIndexer_Get .NET Core 2.0 0.5333 ns 0.0006 ns SpanIndexer_Set .NET 4.6 0.6059 ns 0.0007 ns SpanIndexer_Set .NET Core 1.1 0.6042 ns 0.0002 ns SpanIndexer_Set .NET Core 2.0 0.5205 ns 0.0003 ns Source: Adam Sitnik - https://adamsitnik.com/Span/#using-span
  • 48.
    48 Limitations of Span<T> Heap asgeneric type argument implement any interfaces No parameter of async method
  • 49.
    49 Memory<T> .NET Core 2.1 Asynchronousworld C# 7.2 ReadOnlyMemory<T> On Heap Wrapper of Span<T>
  • 50.
    50 Span<T> and Memory<T>- Supported in .NET Standard 1.0 1.1 1.2 .NET Core 1.0 1.0 1.0 .NET Framework 1 4.5 4.5 4.5.1 Mono 4.6 4.6 4.6 Xamarin.iOS 10.0 10.0 10.0 Xamarin.Mac 3.0 3.0 3.0 Xamarin.Android 7.0 7.0 7.0 Universal Windows Platform 10.0 10.0 10.0 Unity 2018.1 2018.1 2018.1
  • 51.
  • 52.
    52 Json.NET – Highperformance JSON serializer and deserializer .NET Standard 1.6 2.0 2.1 .NET Core 1.0 2.0 3.0 .NET Framework 4.6.1 4.6.1 Won’t support Mono 4.6 5.4 6.4 Xamarin.iOS 10.0 10.14 12.16 Xamarin.Mac 3.0 3.8 5.16 Xamarin.Android 7.0 8.0 10.0 Universal Windows Platform 10.0.16299 10.0. 16299 TBD Unity 2018.1 2018.1 TBD
  • 53.

Editor's Notes

  • #11 Tu myślałem wyjść od jakiegoś use-case'u. Np mamy jakiś duży plik CSV który chcemy sparsować. Prezentujemy stare podejście na streamach, płynnie przechodząc do System.IO.Pipelines i potem BodyReadera/Writera
  • #12 Tu myślałem wyjść od jakiegoś use-case'u. Np mamy jakiś duży plik CSV który chcemy sparsować. Prezentujemy stare podejście na streamach, płynnie przechodząc do System.IO.Pipelines i potem BodyReadera/Writera
  • #13 Tu myślałem wyjść od jakiegoś use-case'u. Np mamy jakiś duży plik CSV który chcemy sparsować. Prezentujemy stare podejście na streamach, płynnie przechodząc do System.IO.Pipelines i potem BodyReadera/Writera
  • #14 KOD!
  • #19 Możemy zaalokować większy bufor, jeżeli natrafimy na dłuższą linię lub rekord zawierający kilka linii. Przy okazji można stosować inne sztuczki, np. ArrayPool w celu minimalizowania liczby alokowanych buforów itp. Itd.. No ok. tylko wtedy przy zwiększaniu bufora kopiujemy pamięć. Dodatkowo warto by było potem zmniejszać ten bufor, żeby nie zajmować niepotrzebnie pamięci….pomysł nie do końca trafiony – generuje duże wykorzystanie pamięci. Możemy poprzedni pomysł rozszerzyć o dodawanie dodatkowych buforów tylko wtedy, kiedy poprzedni zostanie wykorzystany. Potrzebujemy wtedy logiki do odczytywania fragmentów pojedynczego rekordu z wielu buforów, oznaczania zwolnionych już buforów (takich z których odczytaliśmy już dane rekordu) itp. Itd. Całość robi się bardzo skomplikowana… A i tak nie jest do końca efektywnie, bo: Odczyt danych ze strumienia jest uzależniony od szybkości parsowania. Wykorzystanie zwykłej zarządzanej tablicy bajtów może mieć impact na GC – pinned memory, może prowadzić do fragmentacji pamięci.
  • #20 Możemy zaalokować większy bufor, jeżeli natrafimy na dłuższą linię lub rekord zawierający kilka linii. Przy okazji można stosować inne sztuczki, np. ArrayPool w celu minimalizowania liczby alokowanych buforów itp. Itd.. No ok. tylko wtedy przy zwiększaniu bufora kopiujemy pamięć. Dodatkowo warto by było potem zmniejszać ten bufor, żeby nie zajmować niepotrzebnie pamięci….pomysł nie do końca trafiony – generuje duże wykorzystanie pamięci. Możemy poprzedni pomysł rozszerzyć o dodawanie dodatkowych buforów tylko wtedy, kiedy poprzedni zostanie wykorzystany. Potrzebujemy wtedy logiki do odczytywania fragmentów pojedynczego rekordu z wielu buforów, oznaczania zwolnionych już buforów (takich z których odczytaliśmy już dane rekordu) itp. Itd. Całość robi się bardzo skomplikowana… A i tak nie jest do końca efektywnie, bo: Odczyt danych ze strumienia jest uzależniony od szybkości parsowania. Wykorzystanie zwykłej zarządzanej tablicy bajtów może mieć impact na GC – pinned memory, może prowadzić do fragmentacji pamięci.
  • #21 Pipe składa się z dwóch części: PipeWriter – który zapisuje do naszej „rury” oraz PipeReader, który z tej „rury” czyta. Mamy ładnie podzielony proces na 2 składowe – załadowanie danych ze strumienia oraz parsowanie tych danych. Task.WhenAll wskazuje, że obie składowe będą wykonywały się asynchronicznie.
  • #22 Pipe składa się z dwóch części: PipeWriter – który zapisuje do naszej „rury” oraz PipeReader, który z tej „rury” czyta. Mamy ładnie podzielony proces na 2 składowe – załadowanie danych ze strumienia oraz parsowanie tych danych. Task.WhenAll wskazuje, że obie składowe będą wykonywały się asynchronicznie. KOD
  • #26 Jedną z największych zalet wykorzystanie Pipe’ów jest Partial Read – możemy w zasadzie pracować na danych bez ich „skonsumowania”. Przykład ilustruje przykładowy parser HTTP.
  • #27 Pod maską Pipe zarządza (linked) listą zaalokowanych buforów, które są wykorzystywane przez PipeWritera/PipeReadera. PipeReader.ReadAsync zwraca ReadOnlySequence<T>. Za jego pomocą możemy uzyskać dostęp do jednego lub więcej segmentów pamięci (ReadOnlyMemory<T>) – analogicznie do Span<T> i Memory<T> i stringów. Pipe przechowuje informację o położeniu Writera i Readera w kontekście zaalokowanych danych z wykorzystaniem SequencePosition. SequencePosition to nic innego jak wskaźnik na konkretne miejsce we wspomnianej linked liście. ReadOnlySequence<T> i Sequence position to struktury.
  • #28 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #29 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #30 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #31 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #32 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #33 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #34 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #35 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #36 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #37 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #38 W idealnym przypadku to co przychodzi do serwera jest odczytywane przez jeden z wątków i jednocześnie parsowane i przetwarzane przez inny wątek. Samo parsowanie może jednak trwać znacznie dłużej niż ładowanie danych ze strumienia. W tej sytuacji wątek ładujący dane ze strumienia może albo alokować więcej pamięci, albo wstrzymać pracę na jakiś czas - należy znaleźć odpowiedni balans w celu zapewnienia optymalnej wydajności. Do rozwiązania wspomnianego problemu Pipy zostały wyposażone w 2 właściwości – PauseWriterThreshold i ResumeWriterThreshold. Pierwsza definiuje ile danych powinno zostać zbuforowanych zanim wywołanie metody FlushAsync (PipeWriter) spowoduje wstrzymanie pracy Writera. Druga z kolei kontroluje ilość danych którą może zostać skonsumowanych przez PipeReadera zanim praca PipeWritera zostanie wznowiona.
  • #39 Oczywiście w 99% przypadków wgl nie będziemy zajmowali się samodzielnym parsowaniem treści Body. Warto jednak wiedzieć jak to działa pod maską.
  • #40 Oczywiście w 99% przypadków wgl nie będziemy zajmowali się samodzielnym parsowaniem treści Body. Warto jednak wiedzieć jak to działa pod maską.
  • #41 Oczywiście w 99% przypadków wgl nie będziemy zajmowali się samodzielnym parsowaniem treści Body. Warto jednak wiedzieć jak to działa pod maską.
  • #42 Oczywiście w 99% przypadków wgl nie będziemy zajmowali się samodzielnym parsowaniem treści Body. Warto jednak wiedzieć jak to działa pod maską.