<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Sajad Jalilian</title>
    <description>The latest articles on DEV Community by Sajad Jalilian (@sajadjalilian).</description>
    <link>https://dev.to/sajadjalilian</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F800444%2F06e7ae6b-0a51-42c3-aa5a-1d07fa4ab2a0.JPG</url>
      <title>DEV Community: Sajad Jalilian</title>
      <link>https://dev.to/sajadjalilian</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/sajadjalilian"/>
    <language>en</language>
    <item>
      <title>C# Concurrency in a nutshell</title>
      <dc:creator>Sajad Jalilian</dc:creator>
      <pubDate>Fri, 16 May 2025 10:16:14 +0000</pubDate>
      <link>https://dev.to/sajadjalilian/c-concurrency-in-a-nutshell-2c3c</link>
      <guid>https://dev.to/sajadjalilian/c-concurrency-in-a-nutshell-2c3c</guid>
      <description>&lt;p&gt;In this article, I will briefly discuss the most important aspects of concurrency in C#.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrency vs. Parallelism
&lt;/h2&gt;

&lt;blockquote&gt;
&lt;p&gt;A system is said to be concurrent if it can support two or more actions in progress at the same time. A system is said to be parallel if it can support two or more actions executing simultaneously. — The Art of Concurrency book&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;To make breakfast, one person can boil eggs, make tea, and toast bread at the same time. However, they cannot drive to the grocery store and come back simultaneously. To make breakfast and buy groceries in parallel, you need at least two people.&lt;/p&gt;

&lt;p&gt;A computer with a single CPU and a single thread can perform multiple tasks by quickly switching between them. However, to work on multiple jobs simultaneously, multiple threads are required. Each thread acts as a separate worker, allowing the CPU to allocate resources and process data more efficiently. By utilizing multiple threads, a computer can perform jobs in parallel, resulting in faster and more efficient processing.&lt;/p&gt;

&lt;h2&gt;
  
  
  Concurrency in C
&lt;/h2&gt;

&lt;p&gt;To achieve concurrency in C#, you need to understand operating systems, threads, and the C# Asynchronous Pattern.&lt;/p&gt;

&lt;h2&gt;
  
  
  Multithreading
&lt;/h2&gt;

&lt;p&gt;I won’t dive into how to work with threads in this article because we rarely deal with threads in day-to-day code in modern C#. Most of the time, we use the latest C# asynchronous pattern, TAP.&lt;/p&gt;

&lt;p&gt;I recommend an excellent series of articles on &lt;a href="https://www.csharptutorial.net/" rel="noopener noreferrer"&gt;csharptutorial.net&lt;/a&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-thread/" rel="noopener noreferrer"&gt;C# Thread&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-background-thread/" rel="noopener noreferrer"&gt;C# Background Thread&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-threadpool/" rel="noopener noreferrer"&gt;C# Threadpool&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Asynchronous Patterns
&lt;/h2&gt;

&lt;p&gt;C# introduced three asynchronous patterns over its lifetime. First, in C# 1.0, there was the Asynchronous Programming Model (APM) pattern.&lt;/p&gt;

&lt;p&gt;C# 2.0 introduced the Event-based Asynchronous Pattern (EAP).&lt;/p&gt;

&lt;p&gt;Finally, in C# 5.0, we got the latest and greatest of them all: the &lt;a href="https://learn.microsoft.com/en-us/dotnet/csharp/asynchronous-programming/task-asynchronous-programming-model" rel="noopener noreferrer"&gt;Task Asynchronous Programming Model&lt;/a&gt;, or TAP.&lt;/p&gt;

&lt;p&gt;Each pattern simplifies asynchronous programming compared to its predecessors. For those interested in the details, I recommend reading this comprehensive article from the .NET blog; it’s worth your time: &lt;a href="https://devblogs.microsoft.com/dotnet/how-async-await-really-works/" rel="noopener noreferrer"&gt;How Async/Await Really Works in C#&lt;/a&gt;.&lt;/p&gt;

&lt;p&gt;TAP consists of the following key components:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The &lt;em&gt;Task&lt;/em&gt; class — represents an asynchronous operation.&lt;/li&gt;
&lt;li&gt;The &lt;em&gt;async&lt;/em&gt; / &lt;em&gt;await&lt;/em&gt; keywords — define asynchronous methods and wait for the completion of asynchronous operations.&lt;/li&gt;
&lt;li&gt;Task-based APIs — a set of classes that work seamlessly with the Task class and async/await keywords.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What You Should Know About TAP
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-task/" rel="noopener noreferrer"&gt;What is TASK&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-continuewith/" rel="noopener noreferrer"&gt;TASK ContinueWith&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-task-handle-exception/" rel="noopener noreferrer"&gt;Handling Exceptions in C# Task&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-async-await/" rel="noopener noreferrer"&gt;How to Use async/await&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-cancellationtokensource/" rel="noopener noreferrer"&gt;Canceling a Task with CancellationTokenSource&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-whenany/" rel="noopener noreferrer"&gt;TASK WhenAny&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-whenall/" rel="noopener noreferrer"&gt;TASK WhenAll&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  State Machine
&lt;/h2&gt;

&lt;p&gt;When you add the “async” keyword to a method, the compiler creates a &lt;a href="https://refactoring.guru/design-patterns/state" rel="noopener noreferrer"&gt;state&lt;/a&gt; machine in the background. This handles concurrency and frees you from worrying about thread management. However, creating a state machine can be expensive, as the compiler generates a lot of code behind the scenes. Therefore, it’s essential to avoid creating unnecessary state machines.&lt;/p&gt;

&lt;p&gt;Here are some best practices to keep in mind:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Only use “async” in a method when you have an “await” in the method body.&lt;/li&gt;
&lt;li&gt;Don’t use “await” if you don’t need a value returned from a call to an asynchronous method. Just return the “Task” to the upstream caller.&lt;/li&gt;
&lt;li&gt;If you need to use “async” but don’t need to use “await” to get a required value and want to return from the method, use “&lt;a href="https://stackoverflow.com/questions/19568280/what-is-the-use-for-task-fromresulttresult-in-c-sharp" rel="noopener noreferrer"&gt;Task.FromResult&lt;/a&gt;”.&lt;/li&gt;
&lt;li&gt;If you don’t want to return any value from the method, use “&lt;a href="https://stackoverflow.com/questions/30493036/what-is-the-point-of-net-4-6s-task-completedtask" rel="noopener noreferrer"&gt;Task.CompletedTask&lt;/a&gt;”.&lt;/li&gt;
&lt;li&gt;
&lt;a href="https://learn.microsoft.com/en-us/archive/msdn-magazine/2013/march/async-await-best-practices-in-asynchronous-programming#avoid-async-void" rel="noopener noreferrer"&gt;Avoid using “async void” methods&lt;/a&gt;.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Thread Synchronization
&lt;/h2&gt;

&lt;p&gt;Thread synchronization in C# refers to the coordination and control of multiple threads to ensure their safe and orderly execution in a multi-threaded environment. It helps to prevent race conditions, data corruption, and other concurrency-related issues that can occur when multiple threads access shared resources concurrently.&lt;/p&gt;

&lt;h3&gt;
  
  
  Lock
&lt;/h3&gt;

&lt;p&gt;In C#, the &lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-lock/" rel="noopener noreferrer"&gt;lock&lt;/a&gt; keyword is used to ensure mutual exclusion or thread synchronization in a multi-threaded environment. When multiple threads access a shared resource concurrently, using the lock keyword helps prevent race conditions and ensures that only one thread can access the critical section at a time.&lt;/p&gt;

&lt;p&gt;The lock keyword is used in conjunction with a synchronization object, typically an instance of an object. When a thread encounters a lock statement, it attempts to acquire the lock on the specified object. If the lock is already held by another thread, the current thread waits until the lock is released before proceeding.&lt;/p&gt;

&lt;h3&gt;
  
  
  Deadlock
&lt;/h3&gt;

&lt;p&gt;A &lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-continuewith/" rel="noopener noreferrer"&gt;deadlock&lt;/a&gt; is a situation where two or more threads are unable to proceed because each is waiting for a resource held by another thread in the deadlock group. This results in a circular dependency, causing all threads involved to be blocked indefinitely.&lt;/p&gt;

&lt;h3&gt;
  
  
  InterLocked
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.csharptutorial.net/csharp-concurrency/c-interlocked/" rel="noopener noreferrer"&gt;Interlocked&lt;/a&gt; class provides atomic operations for variables shared among multiple threads. It offers thread-safe and atomic operations without the need for explicit locking mechanisms, such as lock or Monitor.&lt;/p&gt;

&lt;h3&gt;
  
  
  ReaderWriterLockSlim
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-readerwriterlockslim/" rel="noopener noreferrer"&gt;ReaderWriterLockSlim&lt;/a&gt; class provides a synchronization mechanism that allows multiple threads to read a shared resource simultaneously while ensuring exclusive access for writing. It is a more efficient alternative to the older ReaderWriterLock class, reducing the overhead associated with acquiring and releasing locks by using lightweight synchronization primitives.&lt;/p&gt;

&lt;h3&gt;
  
  
  SemaphoreSlim
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-semaphoreslim/" rel="noopener noreferrer"&gt;SemaphoreSlim&lt;/a&gt; class provides a synchronization primitive that controls access to a limited number of resources among multiple threads. It is a lightweight alternative to the Semaphore class and is useful in scenarios where you need to limit concurrent access to a shared resource.&lt;/p&gt;

&lt;h2&gt;
  
  
  Thread Signaling
&lt;/h2&gt;

&lt;p&gt;Thread signaling in C# refers to the mechanism of communication and coordination between multiple threads to ensure synchronized execution or to notify each other about specific events or conditions.&lt;/p&gt;

&lt;p&gt;In multi-threaded scenarios, it is often necessary for threads to wait for certain conditions before proceeding or for one thread to notify another about a particular event. This is where thread signaling comes into play.&lt;/p&gt;

&lt;h3&gt;
  
  
  AutoResetEvent
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-autoresetevent/" rel="noopener noreferrer"&gt;AutoResetEvent&lt;/a&gt; class is a synchronization primitive that allows threads to wait until a signal is received and then automatically resets itself. It is a thread-signaling mechanism used for one-to-one thread coordination.&lt;/p&gt;

&lt;h3&gt;
  
  
  ManualResetEventSlim
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-manualreseteventslim/" rel="noopener noreferrer"&gt;ManualResetEventSlim&lt;/a&gt; class is a lightweight synchronization primitive that provides thread-signaling functionality similar to ManualResetEvent. It allows threads to wait until a signal is received and remains in the signaled state until manually reset.&lt;/p&gt;

&lt;h3&gt;
  
  
  CountdownEvent
&lt;/h3&gt;

&lt;p&gt;The &lt;a href="https://www.csharptutorial.net/csharp-concurrency/csharp-countdownevent/" rel="noopener noreferrer"&gt;CountdownEvent&lt;/a&gt; class is a synchronization primitive that allows one or more threads to wait until a specified number of signals or operations have been completed. It provides a way to synchronize the execution of multiple threads and coordinate their completion.&lt;/p&gt;

</description>
      <category>csharp</category>
      <category>dotnet</category>
      <category>concurrency</category>
      <category>parallelism</category>
    </item>
    <item>
      <title>My take on distributed caching</title>
      <dc:creator>Sajad Jalilian</dc:creator>
      <pubDate>Fri, 16 May 2025 10:12:37 +0000</pubDate>
      <link>https://dev.to/sajadjalilian/my-take-on-distributed-caching-1kj2</link>
      <guid>https://dev.to/sajadjalilian/my-take-on-distributed-caching-1kj2</guid>
      <description>&lt;h1&gt;
  
  
  Should I Use Distributed Caching?
&lt;/h1&gt;

&lt;h2&gt;
  
  
  Don’t
&lt;/h2&gt;

&lt;p&gt;The best strategy for distributed caching is often not to use it!&lt;/p&gt;

&lt;p&gt;It’s challenging to implement and adds complexity to your system. As we all know, complexity is the root of most problems in software development.&lt;/p&gt;

&lt;p&gt;So, avoid it if possible.&lt;/p&gt;

&lt;p&gt;Before considering distributed caching, focus on optimizing your application, database, connection, network, or even hardware. Use in-memory caching instead.&lt;/p&gt;

&lt;h2&gt;
  
  
  Use In-Memory Caching
&lt;/h2&gt;

&lt;p&gt;At some point, you’ll need to consider caching to improve performance. Start by leveraging the internal in-memory caching mechanisms provided by your back-end framework, database caching, etc.&lt;/p&gt;

&lt;p&gt;Believe it or not, most tools have built-in caching providers you can utilize.&lt;/p&gt;

&lt;p&gt;In many cases, these simple optimizations can significantly improve your application's performance without the added complexity of distributed caching.&lt;/p&gt;

&lt;h2&gt;
  
  
  Distributed Caching Strategies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  When to Use Distributed Caching
&lt;/h3&gt;

&lt;p&gt;However, as your user base grows, you might need to take caching more seriously. This could mean adding a dedicated caching server or implementing a more complex distributed caching system.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understand Your Workload
&lt;/h2&gt;

&lt;p&gt;To implement caching effectively, you must thoroughly understand your application’s responsibilities and requirements.&lt;/p&gt;

&lt;p&gt;Choosing the right caching pattern depends on your workload. For example, is your application write-heavy, read-heavy, or real-time?&lt;/p&gt;

&lt;h2&gt;
  
  
  Common Caching Strategies
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Cache Aside
&lt;/h3&gt;

&lt;p&gt;This is the most common caching strategy. Here, the cache works alongside the database to reduce database hits. Data is lazy loaded into the cache.&lt;/p&gt;

&lt;p&gt;When a user requests specific data, the system first checks the cache. If the data is present, it’s returned directly. If not, the system fetches the data from the database, updates the cache, and returns the data to the user.&lt;/p&gt;

&lt;p&gt;This strategy works well with read-heavy workloads, such as user profile data (e.g., name, account number) that isn’t frequently updated.&lt;/p&gt;

&lt;p&gt;However, this approach can lead to inconsistencies between the cache and the database. To mitigate this, cached data is assigned a &lt;code&gt;TTL&lt;/code&gt; (Time to Live), after which it’s invalidated.&lt;/p&gt;

&lt;h3&gt;
  
  
  Read-Through
&lt;/h3&gt;

&lt;p&gt;This strategy is similar to Cache Aside but with one key difference: in Read-Through, the cache always stays consistent with the database. The cache library ensures consistency with the back-end.&lt;/p&gt;

&lt;p&gt;Like Cache Aside, data is lazy loaded into the cache when first requested. Developers can also pre-load the cache with frequently requested data to reduce cache misses.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write-Through
&lt;/h3&gt;

&lt;p&gt;In this strategy, all data written to the database goes through the cache. When data is written to the database, the cache is updated simultaneously.&lt;/p&gt;

&lt;p&gt;This ensures a high level of consistency between the cache and the database but adds some latency during write operations. This strategy is well-suited for write-heavy workloads, such as online multiplayer games.&lt;/p&gt;

&lt;h3&gt;
  
  
  Write-Back
&lt;/h3&gt;

&lt;p&gt;Write-Back caching optimizes costs by writing data directly to the cache instead of the database. After a delay (based on business logic), the cache writes the data to the database.&lt;/p&gt;

&lt;p&gt;This approach is ideal for applications with heavy write operations, as it reduces the frequency of database writes and associated costs.&lt;/p&gt;

&lt;p&gt;However, if the cache fails before the database is updated, data loss can occur. This strategy is often combined with other caching strategies for optimal performance.&lt;/p&gt;

&lt;h2&gt;
  
  
  How to Implement Caching
&lt;/h2&gt;

&lt;p&gt;Implementing caching depends on your chosen strategy. For example, when working with a database, you can create a unique key and cache data in Redis.&lt;/p&gt;

&lt;p&gt;Here’s an example of caching in a service:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight csharp"&gt;&lt;code&gt;&lt;span class="k"&gt;public&lt;/span&gt; &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="n"&gt;Task&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;CountByFilterAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;ContentFilter&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="c1"&gt;// Get cache&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cacheKey&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_prefixKey&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="s"&gt;"-CountByFilterAsync-"&lt;/span&gt; &lt;span class="p"&gt;+&lt;/span&gt; &lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CacheHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cacheRepositoryOptions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
    &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_redisCacheEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;GetAsync&lt;/span&gt;&lt;span class="p"&gt;&amp;lt;&lt;/span&gt;&lt;span class="kt"&gt;int&lt;/span&gt;&lt;span class="p"&gt;&amp;gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
        &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="k"&gt;null&lt;/span&gt; &lt;span class="p"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt; &lt;span class="p"&gt;!=&lt;/span&gt; &lt;span class="m"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;cached&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="p"&gt;}&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;_queryableDbSet&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
    &lt;span class="n"&gt;query&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;ApplyFilter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="kt"&gt;var&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;query&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;CountAsync&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;
    &lt;span class="c1"&gt;// Set cache&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;CacheHelper&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;IsEnabled&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;_cacheRepositoryOptions&lt;/span&gt;&lt;span class="p"&gt;))&lt;/span&gt;
        &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="n"&gt;_redisCacheEngine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;SetAsync&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;cacheKey&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;_ttlFromMinutes&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="n"&gt;count&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Best Practices
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Use the &lt;a href="https://codewithmukesh.com/blog/caching-with-mediatr-in-aspnet-core/" rel="noopener noreferrer"&gt;CQRS pipeline&lt;/a&gt; to set cache for Query requests and invalidate it for Command requests.&lt;/li&gt;
&lt;li&gt;Implement the &lt;a href="https://stackoverflow.com/questions/3442102/repository-pattern-caching" rel="noopener noreferrer"&gt;Cached Repository Pattern&lt;/a&gt;.&lt;/li&gt;
&lt;li&gt;Use Event Sourcing to raise events after Commands and asynchronously handle them to invalidate the cache.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Nondeterministic Problem
&lt;/h2&gt;

&lt;h3&gt;
  
  
  Definition
&lt;/h3&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;Nondeterministic functions produce different outputs each time they’re called with the same input values (e.g., the &lt;code&gt;_GETDATE()&lt;/code&gt; function).&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;For deterministic data that doesn’t change frequently, you can cache it and update or invalidate it when the database is updated.&lt;/p&gt;

&lt;p&gt;For nondeterministic data (e.g., a list of contents), caching becomes more complex. For instance, if you cache the first page of content, it might no longer be accurate as data is added, updated, or deleted in the database.&lt;/p&gt;

&lt;p&gt;The best approach for nondeterministic data is to use a &lt;code&gt;TTL&lt;/code&gt;. Start with the shortest TTL you can afford and adjust it based on user behavior and requirements.&lt;/p&gt;

&lt;h2&gt;
  
  
  Redis and StackExchange.Redis
&lt;/h2&gt;

&lt;p&gt;Redis allows you to search for keys with specific prefixes and invalidate them. However, this operation has a time complexity of O(n), which might not be ideal for large datasets. Fortunately, Redis is extremely fast.&lt;/p&gt;

&lt;p&gt;As Redis docs state:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;While the time complexity for this operation is O(N), the constant times are fairly low. For example, Redis running on an entry-level laptop can scan a 1 million key database in 40 milliseconds.&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h3&gt;
  
  
  Key Invalidation
&lt;/h3&gt;

&lt;p&gt;With &lt;code&gt;StackExchange.Redis&lt;/code&gt;, you can invalidate keys by using the &lt;a href="https://stackexchange.github.io/StackExchange.Redis/KeysValues" rel="noopener noreferrer"&gt;Keys method&lt;/a&gt;, which uses &lt;a href="https://redis.io/commands/scan" rel="noopener noreferrer"&gt;SCAN under the hood&lt;/a&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;The&lt;/em&gt; &lt;code&gt;_SCAN_&lt;/code&gt; &lt;em&gt;command allows incremental iteration, returning only a small number of elements per call. This makes it usable in production without the downsides of blocking commands like&lt;/em&gt; &lt;code&gt;_KEYS_&lt;/code&gt; &lt;em&gt;or&lt;/em&gt; &lt;code&gt;_SMEMBERS_&lt;/code&gt;.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;You must iterate over all keys using a cursor, match the desired pattern, and delete the matched keys one by one.&lt;/p&gt;

&lt;h3&gt;
  
  
  Optimizing Performance
&lt;/h3&gt;

&lt;p&gt;To improve performance, store all associated keys in a &lt;code&gt;set&lt;/code&gt; and clear the &lt;code&gt;set&lt;/code&gt; to invalidate everything at once.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Problem with Aggressive Invalidation
&lt;/h2&gt;

&lt;p&gt;Invalidating all keys matching a pattern can be inefficient. For example, updating one piece of content might result in deleting all keys associated with the content entity.&lt;/p&gt;

&lt;p&gt;Instead, use &lt;code&gt;TTL&lt;/code&gt; for nondeterministic data to avoid these issues. With this approach, unused query keys automatically expire after a short time, eliminating the need to find and invalidate associated keys manually. Calibrate the TTL for each endpoint to achieve optimal results.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Be cautious when implementing caching. Use a combination of these strategies to find a solution that works for your specific needs.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;em&gt;There are only two hard things in Computer Science: cache invalidation and naming things. — Phil Karlton&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;

</description>
      <category>dotnet</category>
      <category>redis</category>
      <category>caching</category>
      <category>distributedcaching</category>
    </item>
    <item>
      <title>My Journey Through Python Virtual Environments</title>
      <dc:creator>Sajad Jalilian</dc:creator>
      <pubDate>Fri, 16 May 2025 10:04:12 +0000</pubDate>
      <link>https://dev.to/sajadjalilian/my-journey-through-python-virtual-environments-10il</link>
      <guid>https://dev.to/sajadjalilian/my-journey-through-python-virtual-environments-10il</guid>
      <description>&lt;p&gt;Since I started programming in Python, I was perfectly fine using its standard library tool, &lt;code&gt;venv&lt;/code&gt;, for all my work. You can create a virtual environment using &lt;code&gt;venv&lt;/code&gt; like this:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;python &lt;span class="nt"&gt;-m&lt;/span&gt; venv &amp;lt;venv_name&amp;gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;In the Python ecosystem, there are four well-known tools for creating virtual environments:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;a href="https://docs.python.org/3/library/venv.html" rel="noopener noreferrer"&gt;Standard library venv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://virtualenv.pypa.io/" rel="noopener noreferrer"&gt;Virtualenv package&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://pipenv.pypa.io/" rel="noopener noreferrer"&gt;Pipenv&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href="https://docs.conda.io/projects/Conda/en/latest/user-guide/concepts/environments.html" rel="noopener noreferrer"&gt;Conda environments&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;I won’t dive deep into the differences between these tools, as you can find excellent comparison articles elsewhere. For instance, &lt;a href="https://stackoverflow.com/questions/1534210/use-different-python-version-with-virtualenv" rel="noopener noreferrer"&gt;this StackOverflow thread&lt;/a&gt; is quite useful.&lt;/p&gt;

&lt;p&gt;A while ago, I started working on a project that required three different Python versions: 2.7, 3.3, and 3.8.&lt;/p&gt;

&lt;p&gt;My main concern was finding a straightforward way to use, install, switch between, and create virtual environments for different Python versions.&lt;/p&gt;

&lt;p&gt;Unfortunately, my favorite tool, &lt;code&gt;venv&lt;/code&gt;, does not support specifying the Python version for a virtual environment. To use different Python versions, you first have to install the desired version and then use it to create the virtual environment. The bad news is that &lt;code&gt;venv&lt;/code&gt; was introduced in Python 3.6, so it’s only available for 3.6 and later versions. :( As a result, I had to switch to the &lt;code&gt;Virtualenv&lt;/code&gt; package.&lt;/p&gt;

&lt;p&gt;To create a virtual environment with a specific Python version using &lt;code&gt;virtualenv&lt;/code&gt;, you can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;virtualenv venv &lt;span class="nt"&gt;--python&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;python2.7
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Alternatively, you could use the shiny &lt;code&gt;Pipenv&lt;/code&gt; tool to create virtual environments and enjoy its cool features. However, I found &lt;code&gt;virtualenv&lt;/code&gt; to be good enough for my needs.&lt;/p&gt;

&lt;h3&gt;
  
  
  The Pain of Managing Python Versions
&lt;/h3&gt;

&lt;p&gt;Over time, managing multiple Python versions became a major headache. I had to download old versions, build them, install them, and keep them organized.&lt;/p&gt;

&lt;p&gt;I had previously used &lt;code&gt;Conda environments&lt;/code&gt;, which allow you to install any Python version in an isolated environment. If a specified Python version isn’t already installed, Conda automatically downloads and installs it for you — no manual work needed.&lt;/p&gt;

&lt;p&gt;To create a virtual environment with a specific Python version using Conda, you can do:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;conda create &lt;span class="nt"&gt;-n&lt;/span&gt; myenv &lt;span class="nv"&gt;python&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;3.6
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;I was happy with Conda until I hit another roadblock. It turned out that Conda doesn’t support Python 3.3.7, which I needed for my project. In fact, Conda’s Python version support is much more limited compared to &lt;code&gt;pyenv&lt;/code&gt;, even though Conda uses &lt;code&gt;pyenv&lt;/code&gt; under the hood.&lt;br&gt;&lt;br&gt;
So, once again, I had to try a new tool: my new favorite, &lt;code&gt;pyenv&lt;/code&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  Enter &lt;code&gt;pyenv&lt;/code&gt; and &lt;code&gt;pyenv-virtualenv&lt;/code&gt;
&lt;/h3&gt;

&lt;p&gt;RealPython has an excellent article about using &lt;code&gt;pyenv&lt;/code&gt; (&lt;a href="https://realpython.com/intro-to-pyenv" rel="noopener noreferrer"&gt;check it out here&lt;/a&gt;). With &lt;code&gt;pyenv&lt;/code&gt;, you can easily install and manage different Python versions. Its plugin, &lt;code&gt;pyenv-virtualenv&lt;/code&gt;, allows you to work with virtual environments effortlessly, similar to Conda environments but with more flexibility.&lt;/p&gt;

&lt;h3&gt;
  
  
  My Recommendations
&lt;/h3&gt;

&lt;p&gt;Having used all of these tools, I can say that each has its pros and cons. Here’s my advice:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;If you don’t care about Python versions earlier than 3.6 and don’t need to install more than three different Python versions for the rest of your life, just use the &lt;code&gt;Standard library venv&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;If &lt;code&gt;Conda environments&lt;/code&gt; support all the Python versions you need, go for it.&lt;/li&gt;
&lt;li&gt;If you’re like me and need to use ancient Python versions and expect to add even more in the future, use &lt;code&gt;pyenv&lt;/code&gt; and &lt;code&gt;pyenv-virtualenv&lt;/code&gt; to save yourself a lot of headaches.&lt;/li&gt;
&lt;li&gt;Finally, you can use &lt;code&gt;Pipenv&lt;/code&gt; for &lt;a href="https://realpython.com/pipenv-guide" rel="noopener noreferrer"&gt;entirely different reasons&lt;/a&gt;. The good news is that you can also use it alongside &lt;code&gt;pyenv&lt;/code&gt; (&lt;a href="https://hackernoon.com/reaching-python-development-nirvana-bb5692adf30c" rel="noopener noreferrer"&gt;read more here&lt;/a&gt;).&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Final Thoughts
&lt;/h3&gt;

&lt;p&gt;This is my first English article, so I apologize for any mistakes. Google Docs helped me catch many of my errors in spelling and grammar. I’m learning every day and hope to improve over time.&lt;/p&gt;

</description>
      <category>python</category>
      <category>programming</category>
      <category>virtualenvironments</category>
    </item>
  </channel>
</rss>
