Skip to content

Instantly share code, notes, and snippets.

@pmunin
Last active April 22, 2017 20:35
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save pmunin/446dbf323cc9a67e14a7 to your computer and use it in GitHub Desktop.
Save pmunin/446dbf323cc9a67e14a7 to your computer and use it in GitHub Desktop.
Extensions for convenient using of ReaderWriterLockSlim
<?xml version="1.0" encoding="utf-8"?>
<Project ToolsVersion="14.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProjectGuid>{770EF0F8-9A97-4C82-BD65-343BA94B334C}</ProjectGuid>
<OutputType>Exe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>ReaderWriterLockExtensions</RootNamespace>
<AssemblyName>ReaderWriterLockExtensions</AssemblyName>
<TargetFrameworkVersion>v4.6.1</TargetFrameworkVersion>
<FileAlignment>512</FileAlignment>
<AutoGenerateBindingRedirects>true</AutoGenerateBindingRedirects>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<PlatformTarget>AnyCPU</PlatformTarget>
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Core" />
<Reference Include="System.Xml.Linq" />
<Reference Include="System.Data.DataSetExtensions" />
<Reference Include="Microsoft.CSharp" />
<Reference Include="System.Data" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="..\e09ba0eacbbc31e28d6e\DisposableAction.cs">
<Link>DisposableAction.cs</Link>
</Compile>
<Compile Include="_Sample.cs" />
<Compile Include="ReaderWriterLockSlimExtensions.cs" />
</ItemGroup>
<ItemGroup>
<Folder Include="Properties\" />
</ItemGroup>
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading;
using System.Threading.Tasks;
class _Sample
{
private static object singleton = null;
static void Main(string[] args)
{
using (var rwl = new ReaderWriterLockSlim())
{
//Multithreaded code
rwl.ReadWriteThreadSafe(t =>
{
var res = singleton;
if (res == null) t.WriteAfterReading = () =>
{
singleton = new object();
};
});
}
}
}
using System;
using System.Threading;
/// <summary>
/// Extensions that allow to deal with ReaderWriterLock easier
/// Latest version is here: https://gist.github.com/pmunin/446dbf323cc9a67e14a7
///
/// Depends on:
/// DisposableAction https://gist.github.com/pmunin/e09ba0eacbbc31e28d6e
/// </summary>
public static class ReaderWriterLockSlimExtensions
{
public static IDisposable EnterReadLock(this ReaderWriterLockSlim locker, TimeSpan? timeout)
{
if (!timeout.HasValue)
locker.EnterReadLock();
else if (!locker.TryEnterReadLock(timeout.Value))
throw new TimeoutException();
return new DisposableAction(() => locker.ExitReadLock());
}
/// <summary>
/// Wraps standard lock enter to Disposable, so developer can use "using(locker.EnterReadLock(...))". Dispose will exit lock
/// </summary>
/// <param name="locker"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public static IDisposable EnterWriteLock(this ReaderWriterLockSlim locker, TimeSpan? timeout)
{
if (!timeout.HasValue)
locker.EnterWriteLock();
else if (!locker.TryEnterWriteLock(timeout.Value))
throw new TimeoutException();
return new DisposableAction(() => locker.ExitWriteLock());
}
/// <summary>
/// Wraps standard lock enter to Disposable, so developer can use "using(locker.EnterReadLock(...))". Dispose will exit lock
/// </summary>
/// <param name="locker"></param>
/// <param name="timeout"></param>
/// <returns></returns>
public static IDisposable EnterUpgradeableReadLock(this ReaderWriterLockSlim locker, TimeSpan? timeout)
{
if (!timeout.HasValue)
locker.EnterUpgradeableReadLock();
else if (!locker.TryEnterUpgradeableReadLock(timeout.Value))
throw new TimeoutException();
return new DisposableAction(() => locker.ExitUpgradeableReadLock());
}
public class ThreadSafeReadContext
{
/// <summary>
/// Writing code that should be executed after reading, in upgraded "Lock for Writing" mode (so all readers and writers will wait).
/// </summary>
public Action WriteAfterReading;
public ReaderWriterLockSlim Lock { get; protected set; }
public ThreadSafeReadContext(ReaderWriterLockSlim locker)
{
this.Lock = locker;
}
}
/// <summary>
/// Tries to read resource, making sure no one is writing it at the same time. In read operation decision is made if consequent writing is needed in the continued locking (upgrading lock to Write mode).
/// Algorythm:
/// Locks for reading. executes Delegate that reads resource and analyzes if writing is needed. If writing not needed then it just finishes.
/// If writing is needed, then it locks in upgradeable mode, again executed reading delegate that analyzes if writing is still needed.
/// If writing is still needed then it upgrades lock to writing, executes writing delegate and unlocks.
/// </summary>
/// <param name="locker"></param>
/// <param name="read">reading operation, that will be executed in thread safe context</param>
public static void ReadWriteThreadSafe(this ReaderWriterLockSlim locker, Action<ThreadSafeReadContext> read)
{
var ctx = new ThreadSafeReadContext(locker);
//First try to read current value
//will enter if other thread reading at the same time
//will not enter if something is about to write (UpgradableRead) or writing (Write)
using (locker.EnterReadLock(null))
{
read(ctx);
if (ctx.WriteAfterReading == null) return;
}
ctx.WriteAfterReading = null;
using (locker.EnterUpgradeableReadLock(null)) //Will enter when noone is reading or writing it
{
read(ctx); //During entering if could change
if (ctx.WriteAfterReading == null) return;
using (locker.EnterWriteLock(null));
{
ctx.WriteAfterReading();
}
}
}
public static T ReadWriteThreadSafe<T>(this ReaderWriterLockSlim locker, Func<T> read,
Func<T, bool> isWriteNeeded, Func<T> write)
{
T res = default(T);
locker.ReadWriteThreadSafe(c =>
{
res = read();
if (isWriteNeeded(res))
c.WriteAfterReading = () => { res = write(); };
});
return res;
}
}
@pmunin
Copy link
Author

pmunin commented Feb 3, 2016

  • Change project to unit test

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment