Use System.IO.Pipelines and System.Threading.Channels APIs to Boost Performance
Tags: benchmark, dotnet, dotnetcore, csharp
Published: 2020-09-21 17:15:40

---
title: Use System.IO.Pipelines and System.Threading.Channels APIs to Boost Performance
published: true
date: 2020-09-20 18:03:35 UTC
tags: benchmark,dotnet,dotnetcore,csharp
canonical_url: https://medium.com/@joni2nja/use-system-io-pipelines-and-system-threading-channels-apis-to-boost-performance-832d7ab7c719
---

#### How to use System.IO.Pipelines and System.Threading.Channels APIs to speed up processing

![](https://cdn-images-1.medium.com/max/1024/1*c1szTL0yaZwcsmLkcMcHRA.png)

This post is kind of the continuation of my previous post series: [**Evaluating “ReadLine using System.IO.Pipelines” Performance in C#**](https://dev.to/joni2nja/evaluating-readline-using-system-io-pipelines-performance-in-c-part-2-pmf).

When I wrote those posts, I was in the need of processing a huge text file (hundreds of thousands of lines), read, parse and transform line by line, and then finally save each of them as a text file. Yes, it will end up a ton of files being created!

I was able to speed up the reading time using System.IO.Pipelines APIs as described in the previous post. The result? 10 minutes faster processing time! 🚀

I could have stopped there. But I didn’t. I recalled the new kids on the block: System.Threading.Channels. See Stephen Toub’s excellent post [“An Introduction to System.Threading.Channels”](https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/) for more information.

[An Introduction to System.Threading.Channels | .NET Blog](https://devblogs.microsoft.com/dotnet/an-introduction-to-system-threading-channels/)

Think of the reading line by line as the _Producer_ and the line processing part as _Consumer_. The idea is, to produce as fast as possible using System.IO.Pipelines and consume it— spread the workload concurrently, _asynchronously_, without blocking like BlockingCollection does.

> BlockingCollection involves blocking; there are no task-based APIs to perform work asynchronously. Channels is all about asynchrony; there are no synchronously-blocking APIs — Stephen Toub.

Okay, enough talking. Show me the code!

### Base Implementation

Let’s start from the base implementation (i.e., before using System.Threading.Channels APIs); the code is simply adding each line processing method to the Task list and await on all of them.

_Note that code on this post is slightly adjusted for benchmarks purpose; eliminating unrelated parts as much as possible._

{% gist https://gist.github.com/jo-ninja/c3152aed32fcf287babbaf7daf1410ca %}

We can see the task list growing as we process the line.

![](https://cdn-images-1.medium.com/max/739/1*vqRDgV3owQsxYIlGrxOP9A.png)<figcaption>Task list</figcaption>

### Channel Writer (Producer)

Here is the producer part.

{% gist https://gist.github.com/jo-ninja/29bea4e751cb62b556df7cf430442cbd %}

To help spot the changes quickly, here is the diff:

![Base implementation vs Channel implementation](https://cdn-images-1.medium.com/max/1024/1*XqKqYNIRkYBpZsijHz-6dg.png)<figcaption>Base implementation vs Channel implementation</figcaption>

Should be pretty easy to spot the differences.

### Channel Reader (Consumer)

Finally, the consumer part.

{% gist https://gist.github.com/jo-ninja/504b71d4975f9ba3b8bcc0c1521432f1 %}

Here I defined 3 channel readers and set SingleReader to false (by evaluating ChannelReaderCount == 1). This way, we will have 3 consumers that will process the line concurrently.

This can be observed from the Visual Studio Parallel Stacks window.

![](https://cdn-images-1.medium.com/max/1024/1*WC0B2fsBGmeucKLnaPwzzg.png)<figcaption>Parallel Stacks Window</figcaption>

Tune this value and measure until you get the best performance. Start small, increment the value until you reach to the point where it will give you slower results; it’s the point where you have too many active Task resources, possibly too many context switches.

### Benchmarks Result

Okay, let’s see the benchmarks result.

![](https://cdn-images-1.medium.com/max/1024/1*tsJZld13exelfpVHGeXfQw.png)

**Wow. Almost 2x faster! ** 🚀🚀🚀

Here is the gist version:

{% gist https://gist.github.com/jo-ninja/af4a0a5a492a37a17bbee4a8eb317463 %}

That’s the benchmarks result. What about my case? Well, I saved another 10 minutes, so about 20 minutes faster in total!

### Source Code

You can find the source code in my [GitHub repository](https://github.com/jo-ninja/ReadLinesBenchmarks), branch: _pipelines-and-channels._

### Conclusion

If you have huge text files in size containing hundreds of thousands of lines to be processed, consider to use System.IO.Pipelines for reading and parsing the lines, and combine it with System.Threading.Channels APIs to spread the workload concurrently, asynchronously.