├── .editorconfig ├── .gitignore ├── AsyncCollections.Benchmark ├── .gitignore ├── App.config ├── AsyncBatchQueueBenchmark.cs ├── AsyncCollections.Benchmark.csproj ├── AsyncQueueBenchmark.cs ├── BenchmarkConfig.cs ├── BlockingCollectionAdapter.cs ├── NitoAsyncCollectionAdapter.cs ├── Program.cs ├── Properties │ └── AssemblyInfo.cs ├── TplBatchBlockAdapter.cs ├── TplDataflowAdapter.cs └── packages.config ├── AsyncCollections.Test ├── .gitignore ├── Assertions.cs ├── AsyncBatchQueueTest.cs ├── AsyncBoundedPriorityQueueTest.cs ├── AsyncCollectionTakeFromAnyTest.cs ├── AsyncCollectionTest.cs ├── AsyncCollections.Test.csproj ├── AsyncQueueTest.cs ├── Properties │ └── AssemblyInfo.cs └── packages.config ├── AsyncCollections.sln ├── AsyncCollections ├── .gitignore ├── AnyResult.cs ├── AsyncBatchQueue.cs ├── AsyncBoundedPriorityQueue.cs ├── AsyncCollection.cs ├── AsyncCollections.csproj ├── AsyncQueue.cs ├── AsyncStack.cs ├── IAsyncBatchCollection.cs ├── IAsyncCollection.cs ├── Internal │ ├── BitArray32.cs │ ├── BoxedEnumerator.cs │ ├── CancelledTask.cs │ ├── CompletionSourceAwaiterFactory.cs │ ├── ExclusiveCompletionSourceGroup.cs │ ├── IAwaiter.cs │ ├── IAwaiterFactory.cs │ ├── SelectManyStructEnumererator.cs │ └── TaskExtensions.cs ├── PrioritizedItem.cs ├── Properties │ └── AssemblyInfo.cs ├── TimerAsyncBatchQueue.cs ├── ValueTask │ ├── ConfiguredValueTaskAwaitable.cs │ ├── ValueTask.cs │ └── ValueTaskAwaiter.cs ├── nuget │ └── AsyncCollections.nuspec └── packages.config ├── LICENSE ├── NuGet.Config └── README.md /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | 3 | charset = utf-8-bom 4 | insert_final_newline = true 5 | trim_trailing_whitespace = false 6 | 7 | [*.cs] 8 | 9 | end_of_line = crlf 10 | indent_style = tab 11 | 12 | csharp_style_conditional_delegate_call = true : warning 13 | csharp_style_expression_bodied_accessors = true : warning 14 | csharp_style_expression_bodied_constructors = true : warning 15 | csharp_style_expression_bodied_indexers = true : warning 16 | csharp_style_expression_bodied_methods = true : warning 17 | csharp_style_expression_bodied_operators = true : warning 18 | csharp_style_expression_bodied_properties = true : warning 19 | csharp_style_inlined_variable_declaration = true : warning 20 | csharp_style_pattern_matching_over_as_with_null_check = true : warning 21 | csharp_style_pattern_matching_over_is_with_cast_check = true : warning 22 | csharp_style_throw_expression = true : warning 23 | csharp_style_var_elsewhere = false : warning 24 | csharp_style_var_for_built_in_types = false : warning 25 | csharp_style_var_when_type_is_apparent = false : warning 26 | csharp_new_line_before_catch = true 27 | csharp_new_line_before_else = true 28 | csharp_new_line_before_finally = true 29 | csharp_new_line_before_members_in_anonymous_types = true 30 | csharp_new_line_before_members_in_object_initializers = true 31 | csharp_new_line_before_open_brace = all 32 | csharp_new_line_between_query_expression_clauses = true 33 | 34 | csharp_prefer_braces = false : suggestion 35 | csharp_indent_block_contents = true 36 | csharp_indent_braces = false 37 | csharp_indent_case_contents = true 38 | csharp_indent_case_contents_when_block = false 39 | csharp_indent_switch_labels = true 40 | csharp_indent_labels = no_change 41 | 42 | csharp_space_after_cast = true 43 | csharp_space_after_comma = true 44 | csharp_space_after_dot = false 45 | csharp_space_after_semicolon_in_for_statement = true 46 | csharp_space_before_comma = false 47 | csharp_space_before_dot = false 48 | csharp_space_before_semicolon_in_for_statement = false 49 | csharp_space_after_keywords_in_control_flow_statements = true 50 | csharp_space_around_binary_operators = before_and_after 51 | csharp_space_between_method_declaration_empty_parameter_list_parentheses = false 52 | csharp_space_between_method_declaration_name_and_open_parenthesis = false 53 | csharp_space_between_method_declaration_parameter_list_parentheses = true 54 | csharp_space_between_method_call_name_and_opening_parenthesis = false 55 | csharp_space_between_method_call_empty_parameter_list_parentheses = false 56 | csharp_space_between_method_call_parameter_list_parentheses = true 57 | csharp_space_between_parentheses = control_flow_statements, expressions 58 | csharp_space_before_open_square_brackets = false 59 | csharp_space_between_empty_square_brackets = false 60 | csharp_space_between_square_brackets = true 61 | csharp_space_after_colon_in_inheritance_clause = true 62 | csharp_space_before_colon_in_inheritance_clause = true 63 | csharp_preserve_single_line_statements = false 64 | csharp_preserve_single_line_blocks = true 65 | 66 | dotnet_sort_system_directives_first = true 67 | dotnet_style_coalesce_expression = true : warning 68 | dotnet_style_collection_initializer = true : suggestion 69 | dotnet_style_explicit_tuple_names = true : warning 70 | dotnet_style_null_propagation = true : warning 71 | dotnet_style_object_initializer = true : suggestion 72 | dotnet_style_predefined_type_for_locals_parameters_members = true : warning 73 | dotnet_style_predefined_type_for_member_access = false : warning 74 | dotnet_style_qualification_for_event = false : warning 75 | dotnet_style_qualification_for_field = false : warning 76 | dotnet_style_qualification_for_method = false : warning 77 | dotnet_style_qualification_for_property = false : warning 78 | 79 | # IInterface 80 | 81 | dotnet_naming_rule.interface_rule.symbols = all_interfaces 82 | dotnet_naming_rule.interface_rule.style = interface_style 83 | dotnet_naming_rule.interface_rule.severity = warning 84 | 85 | dotnet_naming_symbols.all_interfaces.applicable_kinds = interface 86 | dotnet_naming_symbols.all_interfaces.applicable_accessibilities = * 87 | 88 | dotnet_naming_style.interface_style.required_prefix = I 89 | dotnet_naming_style.interface_style.capitalization = pascal_case 90 | 91 | # _privateField 92 | 93 | dotnet_naming_rule.private_field_rule.symbols = private_fields 94 | dotnet_naming_rule.private_field_rule.style = private_field_style 95 | dotnet_naming_rule.private_field_rule.severity = warning 96 | 97 | dotnet_naming_symbols.private_fields.applicable_kinds = field 98 | dotnet_naming_symbols.private_fields.applicable_accessibilities = private 99 | 100 | dotnet_naming_style.private_field_style.required_prefix = _ 101 | dotnet_naming_style.private_field_style.capitalization = camel_case 102 | 103 | # parameters 104 | 105 | dotnet_naming_rule.parameter_rule.symbols = parameters 106 | dotnet_naming_rule.parameter_rule.style = camel_case_style 107 | dotnet_naming_rule.parameter_rule.severity = warning 108 | 109 | dotnet_naming_symbols.parameters.applicable_kinds = parameter 110 | 111 | dotnet_naming_style.camel_case_style.capitalization = camel_case 112 | 113 | # Type 114 | 115 | dotnet_naming_rule.type_rule.symbols = all_types 116 | dotnet_naming_rule.type_rule.style = pascal_case_style 117 | dotnet_naming_rule.type_rule.severity = warning 118 | 119 | dotnet_naming_symbols.all_types.applicable_kinds = class, struct, enum 120 | dotnet_naming_symbols.all_types.applicable_accessibilities = * 121 | 122 | # async Task SomeMethodAsync 123 | 124 | dotnet_naming_rule.async_method_rule.symbols = async_methods 125 | dotnet_naming_rule.async_method_rule.style = async_method_style 126 | dotnet_naming_rule.async_method_rule.severity = suggestion 127 | 128 | dotnet_naming_symbols.async_methods.applicable_kinds = method 129 | dotnet_naming_symbols.async_methods.applicable_accessibilities = * 130 | dotnet_naming_symbols.async_methods.required_modifiers = async 131 | 132 | dotnet_naming_style.async_method_style.required_suffix = Async 133 | dotnet_naming_style.async_method_style.capitalization = pascal_case 134 | 135 | # NonFieldMembers 136 | 137 | dotnet_naming_rule.member_rule.symbols = non_field_members 138 | dotnet_naming_rule.member_rule.style = pascal_case_style 139 | dotnet_naming_rule.member_rule.severity = warning 140 | 141 | dotnet_naming_symbols.non_field_members.applicable_kinds = property, method, event 142 | dotnet_naming_symbols.non_field_members.applicable_accessibilities = * 143 | 144 | dotnet_naming_style.pascal_case_style.capitalization = pascal_case 145 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.suo 2 | /packages/ 3 | *.csproj.user 4 | /AsyncCollections.sln.ide/ 5 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /obj/ 3 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/App.config: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/AsyncBatchQueueBenchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | using BenchmarkDotNet.Attributes; 8 | using BenchmarkDotNet.Configs; 9 | using BenchmarkDotNet.Diagnostics.Windows; 10 | using BenchmarkDotNet.Jobs; 11 | using HellBrick.Collections; 12 | 13 | namespace HellBrick.AsyncCollections.Benchmark 14 | { 15 | [Config( typeof( BenchmarkConfig ) )] 16 | public class AsyncBatchQueueBenchmark 17 | { 18 | [Params( 1, 3 )] 19 | public int ConsumerTasks { get; set; } 20 | 21 | [Params( 1, 3 )] 22 | public int ProducerTasks { get; set; } 23 | 24 | private const int _itemsAddedPerThread = 9999; 25 | private const int _batchSize = 32; 26 | 27 | [Benchmark( Description = "HellBrick.AsyncCollections.AsyncBatchQueue" )] 28 | public void HellBrickAsyncBatchQueue() => DdosQueue( new AsyncBatchQueue( _batchSize ) ); 29 | 30 | [Benchmark( Description = "System.Threading.Tasks.Dataflow.BatchBlock" )] 31 | public void DataflowBatchBlock() => DdosQueue( new TplBatchBlockAdapter( _batchSize ) ); 32 | 33 | private void DdosQueue( IAsyncBatchCollection queue ) 34 | { 35 | int itemsAddedTotal = ProducerTasks * _itemsAddedPerThread; 36 | IntHolder itemsTakenHolder = new IntHolder() { Value = 0 }; 37 | CancellationTokenSource consumerCancelSource = new CancellationTokenSource(); 38 | Task[] consumerTasks = Enumerable.Range( 0, ConsumerTasks ) 39 | .Select( _ => Task.Run( () => RunConsumerAsync( queue, itemsTakenHolder, itemsAddedTotal, consumerCancelSource ) ) ) 40 | .ToArray(); 41 | 42 | Task[] producerTasks = Enumerable.Range( 0, ProducerTasks ) 43 | .Select( _ => Task.Run( () => RunProducer( queue ) ) ) 44 | .ToArray(); 45 | 46 | Task.WaitAll( producerTasks ); 47 | Task.WaitAll( consumerTasks ); 48 | } 49 | 50 | private static void RunProducer( IAsyncBatchCollection queue ) 51 | { 52 | for ( int i = 0; i < _itemsAddedPerThread; i++ ) 53 | { 54 | int item = 42; 55 | queue.Add( item ); 56 | } 57 | 58 | queue.Flush(); 59 | } 60 | 61 | private static async Task RunConsumerAsync( IAsyncBatchCollection queue, IntHolder itemsTakeHolder, int itemsAddedTotal, CancellationTokenSource cancelSource ) 62 | { 63 | try 64 | { 65 | CancellationToken cancelToken = cancelSource.Token; 66 | 67 | while ( true ) 68 | { 69 | IReadOnlyList items = await queue.TakeAsync( cancelToken ).ConfigureAwait( false ); 70 | int itemsTakenLocal = Interlocked.Add( ref itemsTakeHolder.Value, items.Count ); 71 | 72 | if ( itemsTakenLocal >= itemsAddedTotal ) 73 | cancelSource.Cancel(); 74 | } 75 | } 76 | catch ( OperationCanceledException ) 77 | { 78 | } 79 | } 80 | 81 | private class IntHolder 82 | { 83 | public int Value; 84 | } 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/AsyncCollections.Benchmark.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {28550F43-E353-44F0-B145-1688B7C85569} 8 | Exe 9 | Properties 10 | HellBrick.AsyncCollections.Benchmark 11 | AsyncCollections.Benchmark 12 | v4.5.1 13 | 512 14 | 15 | 16 | 17 | 18 | 19 | AnyCPU 20 | true 21 | full 22 | false 23 | bin\Debug\ 24 | DEBUG;TRACE 25 | prompt 26 | 4 27 | false 28 | 29 | 30 | AnyCPU 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | false 38 | 39 | 40 | 41 | ..\packages\BenchmarkDotNet.0.9.6\lib\net45\BenchmarkDotNet.dll 42 | True 43 | 44 | 45 | ..\packages\BenchmarkDotNet.Diagnostics.Windows.0.9.6\lib\net40\BenchmarkDotNet.Diagnostics.Windows.dll 46 | True 47 | 48 | 49 | 50 | 51 | 52 | ..\packages\Microsoft.Diagnostics.Tracing.TraceEvent.1.0.41\lib\net40\Microsoft.Diagnostics.Tracing.TraceEvent.dll 53 | True 54 | 55 | 56 | ..\packages\Nito.AsyncEx.3.0.0\lib\net45\Nito.AsyncEx.dll 57 | 58 | 59 | ..\packages\Nito.AsyncEx.3.0.0\lib\net45\Nito.AsyncEx.Concurrent.dll 60 | 61 | 62 | ..\packages\Nito.AsyncEx.3.0.0\lib\net45\Nito.AsyncEx.Enlightenment.dll 63 | 64 | 65 | 66 | 67 | 68 | False 69 | ..\packages\Microsoft.Tpl.Dataflow.4.5.23\lib\portable-net45+win8+wpa81\System.Threading.Tasks.Dataflow.dll 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | {61be7a3d-ac66-434b-8a77-4f3466398cbc} 95 | AsyncCollections 96 | 97 | 98 | 99 | 100 | 101 | 102 | 103 | 104 | 105 | This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. 106 | 107 | 108 | 109 | 116 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/AsyncQueueBenchmark.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using BenchmarkDotNet; 9 | using BenchmarkDotNet.Attributes; 10 | using BenchmarkDotNet.Configs; 11 | using BenchmarkDotNet.Diagnostics.Windows; 12 | using BenchmarkDotNet.Jobs; 13 | using HellBrick.Collections; 14 | 15 | namespace HellBrick.AsyncCollections.Benchmark 16 | { 17 | [Config( typeof( BenchmarkConfig ) )] 18 | public class AsyncQueueBenchmark 19 | { 20 | [Params( 1, 3 )] 21 | public int ConsumerTasks { get; set; } 22 | 23 | [Params( 1, 3 )] 24 | public int ProducerTasks { get; set; } 25 | 26 | private const int _itemsAddedPerThread = 10000; 27 | 28 | [Benchmark( Description = "AsyncQueue" )] 29 | public void HellBrickAsyncQueue() => DdosQueue( new AsyncQueue() ); 30 | 31 | [Benchmark( Description = "AsyncCollection( ConcurrentQueue )" )] 32 | public void HellBrickAsyncCollection() => DdosQueue( new AsyncCollection( new ConcurrentQueue() ) ); 33 | 34 | [Benchmark( Description = "Nito.AsyncEx.AsyncCollection" )] 35 | public void NitoAsyncCollection() => DdosQueue( new NitoAsyncCollectionAdapter() ); 36 | 37 | [Benchmark( Description = "System.Concurrent.BlockingCollection" )] 38 | public void SystemBlockingCollection() => DdosQueue( new BlockingCollectionAdapter() ); 39 | 40 | [Benchmark( Description = "System.Threading.Tasks.Dataflow.BufferBlock" )] 41 | public void DataflowBufferBlock() => DdosQueue( new TplDataflowAdapter() ); 42 | 43 | private void DdosQueue( IAsyncCollection queue ) 44 | { 45 | int itemsAddedTotal = ProducerTasks * _itemsAddedPerThread; 46 | IntHolder itemsTakenHolder = new IntHolder() { Value = 0 }; 47 | CancellationTokenSource consumerCancelSource = new CancellationTokenSource(); 48 | Task[] consumerTasks = Enumerable.Range( 0, ConsumerTasks ) 49 | .Select( _ => Task.Run( () => RunConsumerAsync( queue, itemsTakenHolder, itemsAddedTotal, consumerCancelSource ) ) ) 50 | .ToArray(); 51 | 52 | Task[] producerTasks = Enumerable.Range( 0, ProducerTasks ) 53 | .Select( _ => Task.Run( () => RunProducer( queue ) ) ) 54 | .ToArray(); 55 | 56 | Task.WaitAll( producerTasks ); 57 | Task.WaitAll( consumerTasks ); 58 | } 59 | 60 | private static void RunProducer( IAsyncCollection queue ) 61 | { 62 | for ( int i = 0; i < _itemsAddedPerThread; i++ ) 63 | { 64 | int item = 42; 65 | queue.Add( item ); 66 | } 67 | } 68 | 69 | private static async Task RunConsumerAsync( IAsyncCollection queue, IntHolder itemsTakeHolder, int itemsAddedTotal, CancellationTokenSource cancelSource ) 70 | { 71 | try 72 | { 73 | CancellationToken cancelToken = cancelSource.Token; 74 | 75 | while ( true ) 76 | { 77 | int item = await queue.TakeAsync( cancelToken ).ConfigureAwait( false ); 78 | int itemsTakenLocal = Interlocked.Increment( ref itemsTakeHolder.Value ); 79 | 80 | if ( itemsTakenLocal >= itemsAddedTotal ) 81 | cancelSource.Cancel(); 82 | } 83 | } 84 | catch ( OperationCanceledException ) 85 | { 86 | } 87 | } 88 | 89 | private class IntHolder 90 | { 91 | public int Value; 92 | } 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/BenchmarkConfig.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using BenchmarkDotNet.Configs; 7 | using BenchmarkDotNet.Diagnostics.Windows; 8 | using BenchmarkDotNet.Jobs; 9 | 10 | namespace HellBrick.AsyncCollections.Benchmark 11 | { 12 | internal class BenchmarkConfig : ManualConfig 13 | { 14 | public BenchmarkConfig() 15 | { 16 | Add( Job.RyuJitX64.WithLaunchCount( 1 ) ); 17 | Add( new MemoryDiagnoser() ); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/BlockingCollectionAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | using HellBrick.Collections; 8 | 9 | namespace HellBrick.AsyncCollections.Benchmark 10 | { 11 | class BlockingCollectionAdapter: IAsyncCollection 12 | { 13 | private readonly BlockingCollection _collection; 14 | 15 | public BlockingCollectionAdapter() 16 | { 17 | _collection = new BlockingCollection(); 18 | } 19 | 20 | #region IAsyncCollection Members 21 | 22 | public int AwaiterCount 23 | { 24 | get { throw new NotImplementedException(); } 25 | } 26 | 27 | public void Add( T item ) 28 | { 29 | _collection.Add( item ); 30 | } 31 | 32 | public ValueTask TakeAsync( System.Threading.CancellationToken cancellationToken ) 33 | { 34 | T item = _collection.Take( cancellationToken ); 35 | return new ValueTask( item ); 36 | } 37 | 38 | #endregion 39 | 40 | #region IEnumerable Members 41 | 42 | public IEnumerator GetEnumerator() 43 | { 44 | throw new NotImplementedException(); 45 | } 46 | 47 | #endregion 48 | 49 | #region IEnumerable Members 50 | 51 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 52 | { 53 | throw new NotImplementedException(); 54 | } 55 | 56 | #endregion 57 | 58 | #region IReadOnlyCollection Members 59 | 60 | public int Count 61 | { 62 | get { throw new NotImplementedException(); } 63 | } 64 | 65 | #endregion 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/NitoAsyncCollectionAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using HellBrick.Collections; 7 | 8 | namespace HellBrick.AsyncCollections.Benchmark 9 | { 10 | class NitoAsyncCollectionAdapter: IAsyncCollection 11 | { 12 | private readonly Nito.AsyncEx.AsyncCollection _collection; 13 | 14 | public NitoAsyncCollectionAdapter() 15 | { 16 | _collection = new Nito.AsyncEx.AsyncCollection(); 17 | } 18 | 19 | #region IAsyncCollection Members 20 | 21 | public int AwaiterCount 22 | { 23 | get { throw new NotImplementedException(); } 24 | } 25 | 26 | public void Add( T item ) 27 | { 28 | _collection.Add( item ); 29 | } 30 | 31 | public ValueTask TakeAsync( System.Threading.CancellationToken cancellationToken ) 32 | { 33 | return new ValueTask( _collection.TakeAsync( cancellationToken ) ); 34 | } 35 | 36 | #endregion 37 | 38 | #region IEnumerable Members 39 | 40 | public IEnumerator GetEnumerator() 41 | { 42 | throw new NotImplementedException(); 43 | } 44 | 45 | #endregion 46 | 47 | #region IEnumerable Members 48 | 49 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 50 | { 51 | throw new NotImplementedException(); 52 | } 53 | 54 | #endregion 55 | 56 | #region IReadOnlyCollection Members 57 | 58 | public int Count 59 | { 60 | get { throw new NotImplementedException(); } 61 | } 62 | 63 | #endregion 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/Program.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using BenchmarkDotNet.Running; 7 | 8 | namespace HellBrick.AsyncCollections.Benchmark 9 | { 10 | internal class Program 11 | { 12 | private static void Main( string[] args ) => new BenchmarkSwitcher( EnumerateBenchmarks().ToArray() ).Run(); 13 | 14 | private static IEnumerable EnumerateBenchmarks() 15 | { 16 | yield return typeof( AsyncQueueBenchmark ); 17 | yield return typeof( AsyncBatchQueueBenchmark ); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle( "AsyncCollections.Benchmark" )] 9 | [assembly: AssemblyDescription( "" )] 10 | [assembly: AssemblyConfiguration( "" )] 11 | [assembly: AssemblyCompany( "" )] 12 | [assembly: AssemblyProduct( "AsyncCollections.Benchmark" )] 13 | [assembly: AssemblyCopyright( "Copyright © 2014" )] 14 | [assembly: AssemblyTrademark( "" )] 15 | [assembly: AssemblyCulture( "" )] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible( false )] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid( "2f4828ad-4bee-4575-900b-fd88ffc0ce9b" )] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion( "1.0.0.0" )] 36 | [assembly: AssemblyFileVersion( "1.0.0.0" )] 37 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/TplBatchBlockAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | using System.Threading.Tasks.Dataflow; 9 | using HellBrick.Collections; 10 | 11 | namespace HellBrick.AsyncCollections.Benchmark 12 | { 13 | public class TplBatchBlockAdapter : IAsyncBatchCollection 14 | { 15 | private readonly BatchBlock _batchBlock; 16 | 17 | public TplBatchBlockAdapter( int batchSize ) 18 | { 19 | _batchBlock = new BatchBlock( batchSize ); 20 | } 21 | 22 | public int BatchSize => _batchBlock.BatchSize; 23 | public int Count => _batchBlock.OutputCount; 24 | 25 | public void Add( T item ) => _batchBlock.Post( item ); 26 | public ValueTask> TakeAsync( CancellationToken cancellationToken ) => new ValueTask>( RecieveAsync( cancellationToken ) ); 27 | private async Task> RecieveAsync( CancellationToken cancellationToken ) => await _batchBlock.ReceiveAsync( cancellationToken ).ConfigureAwait( false ); 28 | public void Flush() => _batchBlock.TriggerBatch(); 29 | 30 | public IEnumerator> GetEnumerator() 31 | { 32 | throw new NotImplementedException(); 33 | } 34 | 35 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/TplDataflowAdapter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | using System.Threading.Tasks.Dataflow; 6 | using HellBrick.Collections; 7 | 8 | namespace HellBrick.AsyncCollections.Benchmark 9 | { 10 | class TplDataflowAdapter: IAsyncCollection 11 | { 12 | private readonly BufferBlock buffer = new BufferBlock(); 13 | 14 | public int AwaiterCount 15 | { 16 | get { throw new NotSupportedException(); } 17 | } 18 | 19 | public void Add( T item ) 20 | { 21 | buffer.Post( item ); 22 | } 23 | 24 | public ValueTask TakeAsync( CancellationToken cancellationToken ) 25 | { 26 | return new ValueTask( buffer.ReceiveAsync( cancellationToken ) ); 27 | } 28 | 29 | public IEnumerator GetEnumerator() 30 | { 31 | throw new NotImplementedException(); 32 | } 33 | 34 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() 35 | { 36 | throw new NotImplementedException(); 37 | } 38 | 39 | public int Count 40 | { 41 | get { throw new NotImplementedException(); } 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /AsyncCollections.Benchmark/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /AsyncCollections.Test/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /obj/ 3 | -------------------------------------------------------------------------------- /AsyncCollections.Test/Assertions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using FluentAssertions.Collections; 8 | 9 | namespace HellBrick.Collections.Test 10 | { 11 | public static class Assertions 12 | { 13 | /// 14 | /// Expects the current collection to contain all elements of in that exact order. 15 | /// 16 | public static AndConstraint> BeEqualTo( this GenericCollectionAssertions collectionShould, IReadOnlyList expected ) 17 | { 18 | AndConstraint> andConstraint = collectionShould.HaveSameCount( expected ); 19 | 20 | for ( int i = 0; i < expected.Count; i++ ) 21 | andConstraint = collectionShould.HaveElementAt( i, expected[ i ] ); 22 | 23 | return andConstraint; 24 | } 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /AsyncCollections.Test/AsyncBatchQueueTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Text; 4 | using System.Collections.Generic; 5 | using System.Threading.Tasks; 6 | using System.Threading; 7 | using FluentAssertions; 8 | using Xunit; 9 | 10 | namespace HellBrick.Collections.Test 11 | { 12 | public class AsyncBatchQueueTest 13 | { 14 | private AsyncBatchQueue _queue; 15 | 16 | [Fact] 17 | public void ThrowsOnIncorrectBatchSize() 18 | { 19 | Action act = () => _queue = new AsyncBatchQueue( 0 ); 20 | act.ShouldThrow(); 21 | } 22 | 23 | [Fact] 24 | public async Task FlushesWhenBatchSizeIsReached() 25 | { 26 | int[] array = { 0, 1, 42 }; 27 | int index = 0; 28 | 29 | _queue = new AsyncBatchQueue( array.Length ); 30 | for ( ; index < array.Length - 1; index++ ) 31 | _queue.Add( array[ index ] ); 32 | 33 | var takeTask = _queue.TakeAsync(); 34 | takeTask.IsCompleted.Should().BeFalse(); 35 | 36 | _queue.Add( array[ index ] ); 37 | var batch = await takeTask.ConfigureAwait( true ); 38 | 39 | batch.Should().BeEqualTo( array ); 40 | } 41 | 42 | [Fact] 43 | public async Task ManualFlushWorks() 44 | { 45 | int[] array = { 0, 1, 42 }; 46 | 47 | _queue = new AsyncBatchQueue( 50 ); 48 | foreach ( var item in array ) 49 | _queue.Add( item ); 50 | 51 | _queue.Flush(); 52 | var batch = await _queue.TakeAsync().ConfigureAwait( true ); 53 | 54 | batch.Should().BeEqualTo( array ); 55 | } 56 | 57 | [Fact] 58 | public async Task TimerFlushesPendingItems() 59 | { 60 | TimeSpan flushPeriod = TimeSpan.FromMilliseconds( 500 ); 61 | var timerQueue = new AsyncBatchQueue( 9999 ).WithFlushEvery( flushPeriod ); 62 | timerQueue.Add( 42 ); 63 | 64 | await Task.Delay( flushPeriod + flushPeriod ).ConfigureAwait( true ); 65 | var batch = await timerQueue.TakeAsync().ConfigureAwait( true ); 66 | batch.Should().BeEqualTo( new[] { 42 } ); 67 | } 68 | 69 | [Fact] 70 | public async Task MultithreadingInsertsDontCrash() 71 | { 72 | int insertThreads = 4; 73 | int itemsPerThread = 100; 74 | 75 | _queue = new AsyncBatchQueue( 11 ); 76 | 77 | List insertTasks = Enumerable.Range( 1, insertThreads ) 78 | .Select( 79 | _ => Task.Run( 80 | () => 81 | { 82 | for ( int i = 0; i < itemsPerThread; i++ ) 83 | _queue.Add( 42 ); 84 | } ) ) 85 | .ToList(); 86 | 87 | await Task.WhenAll( insertTasks ).ConfigureAwait( true ); 88 | _queue.Flush(); 89 | 90 | int itemsTaken = 0; 91 | while ( _queue.Count > 0 ) 92 | itemsTaken += ( await _queue.TakeAsync().ConfigureAwait( true ) ).Count; 93 | 94 | itemsTaken.Should().Be( insertThreads * itemsPerThread ); 95 | } 96 | 97 | [Fact] 98 | public async Task NoRaceBetweenFlushOnAddAndOnDemand() 99 | { 100 | const int attempts = 100 * 1000; 101 | const int batchSize = 5; 102 | _queue = new AsyncBatchQueue( batchSize ); 103 | 104 | for ( int attemptNumber = 0; attemptNumber < attempts; attemptNumber++ ) 105 | { 106 | AddAllItemsButOne( batchSize ); 107 | 108 | using ( ManualResetEvent trigger = new ManualResetEvent( initialState: false ) ) 109 | { 110 | Task addTask = Task.Run 111 | ( 112 | () => 113 | { 114 | trigger.WaitOne(); 115 | _queue.Add( 666 ); 116 | } 117 | ); 118 | 119 | Task flushTask = Task.Run 120 | ( 121 | () => 122 | { 123 | trigger.WaitOne(); 124 | _queue.Flush(); 125 | } 126 | ); 127 | 128 | trigger.Set(); 129 | await addTask.ConfigureAwait( true ); 130 | await flushTask.ConfigureAwait( true ); 131 | 132 | IReadOnlyList batch = await _queue.TakeAsync().ConfigureAwait( true ); 133 | List allItems = batch.ToList(); 134 | 135 | // This happens if Flush occurred before Add, which means there's another item from Add left unflushed. 136 | // Gotta flush once more to extract it. 137 | if ( batch.Count < batchSize ) 138 | { 139 | _queue.Flush(); 140 | IReadOnlyList secondBatch = await _queue.TakeAsync().ConfigureAwait( true ); 141 | allItems.AddRange( secondBatch ); 142 | } 143 | 144 | allItems.Count.Should().BeLessOrEqualTo( batchSize, $"Double flush detected at attempt #{attemptNumber}. Items: {String.Join( ", ", allItems )}" ); 145 | } 146 | } 147 | } 148 | 149 | private void AddAllItemsButOne( int batchSize ) 150 | { 151 | for ( int itemIndex = 0; itemIndex < batchSize - 1; itemIndex++ ) 152 | _queue.Add( itemIndex ); 153 | } 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /AsyncCollections.Test/AsyncBoundedPriorityQueueTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | using FluentAssertions; 7 | using HellBrick.Collections; 8 | using Xunit; 9 | 10 | namespace HellBrick.Collections.Test 11 | { 12 | public class AsyncBoundedPriorityQueueTest: AsyncCollectionTest> 13 | { 14 | protected override AsyncBoundedPriorityQueue CreateCollection() 15 | { 16 | return new AsyncBoundedPriorityQueue( 2 ); 17 | } 18 | 19 | [Fact] 20 | public async Task ReturnsLowPriorityIfNoHighPriorityIsAvailable() 21 | { 22 | Collection.Add( 42, 1 ); 23 | var result = await Collection.TakeAsync().ConfigureAwait( true ); 24 | result.Should().Be( new PrioritizedItem( 42, 1 ) ); 25 | } 26 | 27 | [Fact] 28 | public async Task RespectsPriority() 29 | { 30 | Collection.Add( 42, 0 ); 31 | Collection.Add( 999, 1 ); 32 | 33 | var result = await Collection.TakeAsync().ConfigureAwait( true ); 34 | result.Should().Be( new PrioritizedItem( 42, 0 ) ); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /AsyncCollections.Test/AsyncCollectionTakeFromAnyTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using FluentAssertions; 10 | using HellBrick.Collections; 11 | using Xunit; 12 | 13 | namespace HellBrick.Collections.Test 14 | { 15 | public class AsyncCollectionTakeFromAnyTest 16 | { 17 | private readonly AsyncCollection[] _collections; 18 | 19 | public AsyncCollectionTakeFromAnyTest() 20 | { 21 | _collections = new AsyncCollection[ 2 ] { new AsyncCollection( new ConcurrentQueue() ), new AsyncCollection( new ConcurrentQueue() ) }; 22 | } 23 | 24 | [Fact] 25 | public async Task ReturnsItemFromSecondIfFirstIsEmpty() 26 | { 27 | _collections[ 1 ].Add( 42 ); 28 | 29 | var result = await AsyncCollection.TakeFromAnyAsync( _collections ).ConfigureAwait( true ); 30 | result.Value.Should().Be( 42 ); 31 | result.CollectionIndex.Should().Be( 1 ); 32 | } 33 | 34 | [Fact] 35 | public async Task NoUnnecessaryAwaitersAreQueued() 36 | { 37 | _collections[ 1 ].Add( 42 ); 38 | 39 | var result = await AsyncCollection.TakeFromAnyAsync( _collections ).ConfigureAwait( true ); 40 | _collections[ 0 ].AwaiterCount.Should().Be( 0 ); 41 | } 42 | 43 | [Fact] 44 | public async Task RespectsCollectionOrder() 45 | { 46 | _collections[ 0 ].Add( 42 ); 47 | _collections[ 1 ].Add( 24 ); 48 | 49 | var result = await AsyncCollection.TakeFromAnyAsync( _collections ).ConfigureAwait( true ); 50 | result.Value.Should().Be( 42 ); 51 | result.CollectionIndex.Should().Be( 0 ); 52 | } 53 | 54 | [Fact] 55 | public async Task ReturnsItemIfItIsAddedLater() 56 | { 57 | var task = AsyncCollection.TakeFromAnyAsync( _collections ); 58 | task.IsCompleted.Should().BeFalse(); 59 | 60 | _collections[ 1 ].Add( 42 ); 61 | var result = await task.ConfigureAwait( true ); 62 | result.Value.Should().Be( 42 ); 63 | result.CollectionIndex.Should().Be( 1 ); 64 | } 65 | 66 | [Fact] 67 | public void CancelsTaskWhenTokenIsCanceled() 68 | { 69 | CancellationTokenSource cancelSource = new CancellationTokenSource(); 70 | var task = AsyncCollection.TakeFromAnyAsync( _collections, cancelSource.Token ); 71 | 72 | cancelSource.Cancel(); 73 | Func asyncAct = async () => await task; 74 | asyncAct.ShouldThrow(); 75 | 76 | _collections[ 0 ].Add( 42 ); 77 | _collections[ 1 ].Add( 64 ); 78 | _collections[ 0 ].Count.Should().Be( 1 ); 79 | _collections[ 1 ].Count.Should().Be( 1 ); 80 | } 81 | 82 | [Fact] 83 | public void DoesNothingIfTokenIsCanceledBeforeMethodCall() 84 | { 85 | CancellationTokenSource cancelSource = new CancellationTokenSource(); 86 | cancelSource.Cancel(); 87 | _collections[ 0 ].Add( 42 ); 88 | 89 | var task = AsyncCollection.TakeFromAnyAsync( _collections, cancelSource.Token ); 90 | 91 | task.IsCanceled.Should().BeTrue(); 92 | _collections[ 0 ].Count.Should().Be( 1 ); 93 | } 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /AsyncCollections.Test/AsyncCollectionTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Diagnostics; 4 | using System.Linq; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using FluentAssertions; 10 | using HellBrick.Collections; 11 | using Xunit; 12 | 13 | namespace HellBrick.Collections.Test 14 | { 15 | public abstract class AsyncCollectionTest where TAsyncCollection : IAsyncCollection 16 | { 17 | protected TAsyncCollection Collection { get; } 18 | 19 | protected AsyncCollectionTest() 20 | { 21 | Collection = CreateCollection(); 22 | } 23 | 24 | protected abstract TAsyncCollection CreateCollection(); 25 | 26 | [Fact] 27 | public void TakingItemFromNonEmptyCollectionCompletesImmediately() 28 | { 29 | Collection.Add( 42 ); 30 | var itemTask = Collection.TakeAsync( CancellationToken.None ); 31 | itemTask.IsCompleted.Should().BeTrue(); 32 | itemTask.Result.Should().Be( 42 ); 33 | } 34 | 35 | [Fact] 36 | public async Task AddingItemCompletesPendingTask() 37 | { 38 | var itemTask = Collection.TakeAsync( CancellationToken.None ); 39 | itemTask.IsCompleted.Should().BeFalse(); 40 | 41 | Collection.Add( 42 ); 42 | ( await itemTask.ConfigureAwait( true ) ).Should().Be( 42 ); 43 | } 44 | 45 | [Fact] 46 | public void TakeWithCanceledTokenReturnsCanceledTask() 47 | { 48 | CancellationTokenSource cancelSource = new CancellationTokenSource(); 49 | cancelSource.Cancel(); 50 | Collection.Add( 42 ); 51 | 52 | ValueTask itemTask = Collection.TakeAsync( cancelSource.Token ); 53 | itemTask.IsCanceled.Should().BeTrue( "The task should have been canceled." ); 54 | } 55 | 56 | [Fact] 57 | public void CancelledTakeCancelsTask() 58 | { 59 | CancellationTokenSource cancelSource = new CancellationTokenSource(); 60 | var itemTask = Collection.TakeAsync( cancelSource.Token ); 61 | cancelSource.Cancel(); 62 | 63 | Func asyncAct = async () => await itemTask; 64 | asyncAct.ShouldThrow(); 65 | 66 | Collection.Add( 42 ); 67 | Collection.Count.Should().Be( 1 ); 68 | Collection.AwaiterCount.Should().Be( 0 ); 69 | } 70 | 71 | [Fact] 72 | public void InsertedItemsCanBeEnumerated() 73 | { 74 | int[] items = Enumerable.Range( 0, 1000 ).ToArray(); 75 | foreach ( int item in items ) 76 | Collection.Add( item ); 77 | 78 | int[] enumeratedItems = Collection.ToArray(); 79 | enumeratedItems.Should().BeEquivalentTo( items ); 80 | } 81 | 82 | [Fact] 83 | public void ContinuationIsNotInlinedOnAddThread() 84 | { 85 | Task takeTask = TakeAndReturnContinuationThreadIdAsync(); 86 | int addThreadID = Thread.CurrentThread.ManagedThreadId; 87 | Collection.Add( 42 ); 88 | int continuationThreadID = takeTask.GetAwaiter().GetResult(); 89 | 90 | addThreadID.Should().NotBe( continuationThreadID, "TakeAsync() continuation shouldn't have been inlined on the Add() thread." ); 91 | } 92 | 93 | private async Task TakeAndReturnContinuationThreadIdAsync() 94 | { 95 | await Collection.TakeAsync().ConfigureAwait( false ); 96 | return Thread.CurrentThread.ManagedThreadId; 97 | } 98 | 99 | [Fact] 100 | public async Task RandomMultithreadingOperationsDontCrash() 101 | { 102 | int itemsTaken = 0; 103 | int itemCount = 100; 104 | int producerThreads = 4; 105 | int totalItemCount = itemCount * producerThreads; 106 | int consumerThreads = 2; 107 | CancellationTokenSource cancelSource = new CancellationTokenSource(); 108 | 109 | List consumerTasks = new List(); 110 | 111 | for ( int i = 0; i < consumerThreads; i++ ) 112 | { 113 | int consumerID = i; 114 | var consumerTask = Task.Run( 115 | async () => 116 | { 117 | try 118 | { 119 | while ( itemsTaken < totalItemCount ) 120 | { 121 | int item = await Collection.TakeAsync( cancelSource.Token ).ConfigureAwait( true ); 122 | int itemsTakenLocal = Interlocked.Increment( ref itemsTaken ); 123 | if ( itemsTakenLocal == totalItemCount ) 124 | cancelSource.Cancel(); 125 | 126 | Debug.WriteLine( "{0} ( - {1} by {2} )", Collection, item, consumerID ); 127 | } 128 | } 129 | catch ( OperationCanceledException ) 130 | { 131 | // This is expected 132 | } 133 | } ); 134 | 135 | consumerTasks.Add( consumerTask ); 136 | } 137 | 138 | List producerTasks = new List(); 139 | 140 | for ( int i = 0; i < producerThreads; i++ ) 141 | { 142 | int producerID = i; 143 | 144 | var producerTask = Task.Run( 145 | () => 146 | { 147 | for ( int j = 0; j < itemCount; j++ ) 148 | { 149 | int item = producerID * itemCount + j; // some kind of a unique item ID 150 | Collection.Add( item ); 151 | Debug.WriteLine( Collection ); 152 | } 153 | } ); 154 | 155 | producerTasks.Add( producerTask ); 156 | } 157 | 158 | await Task.WhenAll( producerTasks ).ConfigureAwait( true ); 159 | 160 | await Task.WhenAll( consumerTasks ).ConfigureAwait( true ); 161 | Collection.Count.Should().Be( 0 ); 162 | } 163 | } 164 | 165 | public class AsyncStackTest : AsyncCollectionTest> 166 | { 167 | protected override AsyncStack CreateCollection() => new AsyncStack(); 168 | } 169 | } 170 | -------------------------------------------------------------------------------- /AsyncCollections.Test/AsyncCollections.Test.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | Debug 5 | AnyCPU 6 | {EAA37D17-2AF6-447E-A568-97F061C9D1D7} 7 | Library 8 | Properties 9 | HellBrick.Collections.Test 10 | AsyncCollections.Test 11 | v4.5.1 12 | 512 13 | {3AC096D0-A1C2-E12C-1390-A8335801FDAB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 14 | 10.0 15 | $(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion) 16 | $(ProgramFiles)\Common Files\microsoft shared\VSTT\$(VisualStudioVersion)\UITestExtensionPackages 17 | False 18 | UnitTest 19 | 20 | 21 | 22 | true 23 | full 24 | false 25 | bin\Debug\ 26 | DEBUG;TRACE 27 | prompt 28 | 4 29 | 30 | 31 | pdbonly 32 | true 33 | bin\Release\ 34 | TRACE 35 | prompt 36 | 4 37 | 38 | 39 | 40 | ..\packages\FluentAssertions.4.5.0\lib\net45\FluentAssertions.dll 41 | True 42 | 43 | 44 | ..\packages\FluentAssertions.4.5.0\lib\net45\FluentAssertions.Core.dll 45 | True 46 | 47 | 48 | 49 | 50 | 51 | ..\packages\xunit.abstractions.2.0.0\lib\net35\xunit.abstractions.dll 52 | True 53 | 54 | 55 | ..\packages\xunit.assert.2.1.0\lib\dotnet\xunit.assert.dll 56 | True 57 | 58 | 59 | ..\packages\xunit.extensibility.core.2.1.0\lib\dotnet\xunit.core.dll 60 | True 61 | 62 | 63 | ..\packages\xunit.extensibility.execution.2.1.0\lib\net45\xunit.execution.desktop.dll 64 | True 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | {61be7a3d-ac66-434b-8a77-4f3466398cbc} 87 | AsyncCollections 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | 98 | 99 | 100 | False 101 | 102 | 103 | False 104 | 105 | 106 | False 107 | 108 | 109 | False 110 | 111 | 112 | 113 | 114 | 115 | 116 | 123 | -------------------------------------------------------------------------------- /AsyncCollections.Test/AsyncQueueTest.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Linq; 3 | using System.Threading.Tasks; 4 | using FluentAssertions; 5 | using Xunit; 6 | 7 | namespace HellBrick.Collections.Test 8 | { 9 | public class AsyncQueueTest : AsyncCollectionTest> 10 | { 11 | private const int _itemsToOverflowSegment = AsyncQueue.SegmentSize + 1; 12 | 13 | protected override AsyncQueue CreateCollection() => new AsyncQueue(); 14 | 15 | [Theory] 16 | [InlineData( 0, 0 )] 17 | [InlineData( 5, 3 )] 18 | [InlineData( 3, 5 )] 19 | [InlineData( 7, 7 )] 20 | [InlineData( AsyncQueue.SegmentSize, 1 )] 21 | [InlineData( 1, AsyncQueue.SegmentSize )] 22 | [InlineData( _itemsToOverflowSegment, 1 )] 23 | [InlineData( 1, _itemsToOverflowSegment )] 24 | [InlineData( _itemsToOverflowSegment * 2, 1 )] 25 | [InlineData( 1, _itemsToOverflowSegment * 2 )] 26 | public void CountsAreCorrect( int itemsInserted, int awaitersInserted ) 27 | { 28 | InsertItems( Enumerable.Range( 0, itemsInserted ).ToArray() ); 29 | InsertAwaiters( awaitersInserted ); 30 | 31 | int itemAwaiterBalance = itemsInserted - awaitersInserted; 32 | 33 | Collection.Count.Should().Be( Math.Max( 0, itemAwaiterBalance ) ); 34 | Collection.AwaiterCount.Should().Be( Math.Max( 0, -1 * itemAwaiterBalance ) ); 35 | } 36 | 37 | [Fact] 38 | public void CountsAreCorrectIfItemTailLagsBehind() 39 | { 40 | InsertAwaiters( _itemsToOverflowSegment ); 41 | 42 | Collection.Count.Should().Be( 0 ); 43 | Collection.AwaiterCount.Should().Be( _itemsToOverflowSegment ); 44 | 45 | InsertItems( Enumerable.Range( 0, 1 ).ToArray() ); 46 | 47 | Collection.Count.Should().Be( 0 ); 48 | Collection.AwaiterCount.Should().Be( _itemsToOverflowSegment - 1 ); 49 | } 50 | 51 | [Fact] 52 | public void CountsAreCorrectIfAwaiterTailLagsBehind() 53 | { 54 | InsertItems( Enumerable.Range( 0, _itemsToOverflowSegment ).ToArray() ); 55 | 56 | Collection.AwaiterCount.Should().Be( 0 ); 57 | Collection.Count.Should().Be( _itemsToOverflowSegment ); 58 | 59 | InsertAwaiters( 1 ); 60 | 61 | Collection.AwaiterCount.Should().Be( 0 ); 62 | Collection.Count.Should().Be( _itemsToOverflowSegment - 1 ); 63 | } 64 | 65 | [Fact] 66 | public void CountsAreCorrectIfTailsMatch() 67 | { 68 | InsertAwaiters( _itemsToOverflowSegment + 1 ); 69 | InsertItems( Enumerable.Range( 0, _itemsToOverflowSegment ).ToArray() ); 70 | 71 | Collection.Count.Should().Be( 0 ); 72 | Collection.AwaiterCount.Should().Be( 1 ); 73 | 74 | Collection.Add( 42 ); 75 | 76 | Collection.Count.Should().Be( 0 ); 77 | Collection.AwaiterCount.Should().Be( 0 ); 78 | 79 | Collection.Add( 64 ); 80 | 81 | Collection.Count.Should().Be( 1 ); 82 | Collection.AwaiterCount.Should().Be( 0 ); 83 | } 84 | 85 | [Theory] 86 | [InlineData( Order.ItemsFirst )] 87 | [InlineData( Order.AwaitersFirst )] 88 | public async Task EverythingWorksIfSegmentIsFilledByOneKindOfItems( Order insertionOrder ) 89 | { 90 | int[] items = Enumerable.Range( 0, _itemsToOverflowSegment ).ToArray(); 91 | ValueTask[] tasks = null; 92 | 93 | switch ( insertionOrder ) 94 | { 95 | case Order.ItemsFirst: 96 | InsertItems( items ); 97 | tasks = InsertAwaiters( items.Length ); 98 | break; 99 | 100 | case Order.AwaitersFirst: 101 | tasks = InsertAwaiters( items.Length ); 102 | InsertItems( items ); 103 | break; 104 | } 105 | 106 | tasks.Should().OnlyContain( t => t.IsCompleted ); 107 | int[] values = await Task.WhenAll( tasks.Select( t => t.AsTask() ) ).ConfigureAwait( true ); 108 | values.Should().BeEquivalentTo( items ).And.BeInAscendingOrder(); 109 | } 110 | 111 | [Fact] 112 | public void EnumeratorReturnsItemsInCorrectOrder() 113 | { 114 | int[] items = Enumerable.Range( 0, _itemsToOverflowSegment * 2 ).ToArray(); 115 | InsertItems( items ); 116 | Collection.Should().BeEquivalentTo( items ).And.BeInAscendingOrder(); 117 | } 118 | 119 | [Fact] 120 | public void EnumeratorDoesNotReturnItemsThatHaveBeenRemovedBetweenMoveNextCalls() 121 | { 122 | InsertItems( 1, 2, 3 ); 123 | using ( var enumerator = Collection.GetEnumerator() ) 124 | { 125 | enumerator.MoveNext().Should().BeTrue(); 126 | enumerator.Current.Should().Be( 1 ); 127 | 128 | InsertAwaiters( 2 ); 129 | 130 | enumerator.MoveNext().Should().BeTrue(); 131 | enumerator.Current.Should().Be( 3 ); 132 | } 133 | } 134 | 135 | private ValueTask[] InsertAwaiters( int awaiterCount ) => Enumerable.Repeat( 0, awaiterCount ).Select( _ => Collection.TakeAsync() ).ToArray(); 136 | 137 | private void InsertItems( params int[] items ) 138 | { 139 | foreach ( int item in items ) 140 | Collection.Add( item ); 141 | } 142 | 143 | public enum Order 144 | { 145 | ItemsFirst, 146 | AwaitersFirst 147 | } 148 | } 149 | } 150 | -------------------------------------------------------------------------------- /AsyncCollections.Test/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle( "AsyncCollections.Test" )] 9 | [assembly: AssemblyDescription( "" )] 10 | [assembly: AssemblyConfiguration( "" )] 11 | [assembly: AssemblyCompany( "" )] 12 | [assembly: AssemblyProduct( "AsyncCollections.Test" )] 13 | [assembly: AssemblyCopyright( "Copyright © 2014" )] 14 | [assembly: AssemblyTrademark( "" )] 15 | [assembly: AssemblyCulture( "" )] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible( false )] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid( "1c03447a-1eeb-4dd7-848c-a16b6f29f3b2" )] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion( "1.0.0.0" )] 36 | [assembly: AssemblyFileVersion( "1.0.0.0" )] 37 | -------------------------------------------------------------------------------- /AsyncCollections.Test/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /AsyncCollections.sln: -------------------------------------------------------------------------------- 1 |  2 | Microsoft Visual Studio Solution File, Format Version 12.00 3 | # Visual Studio 2013 4 | VisualStudioVersion = 12.0.21005.1 5 | MinimumVisualStudioVersion = 10.0.40219.1 6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncCollections", "AsyncCollections\AsyncCollections.csproj", "{61BE7A3D-AC66-434B-8A77-4F3466398CBC}" 7 | EndProject 8 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncCollections.Test", "AsyncCollections.Test\AsyncCollections.Test.csproj", "{EAA37D17-2AF6-447E-A568-97F061C9D1D7}" 9 | EndProject 10 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "AsyncCollections.Benchmark", "AsyncCollections.Benchmark\AsyncCollections.Benchmark.csproj", "{28550F43-E353-44F0-B145-1688B7C85569}" 11 | EndProject 12 | Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = ".nuget", ".nuget", "{45DBD4B2-E839-4D97-97A8-686E8E6B4645}" 13 | ProjectSection(SolutionItems) = preProject 14 | NuGet.Config = NuGet.Config 15 | EndProjectSection 16 | EndProject 17 | Global 18 | GlobalSection(SolutionConfigurationPlatforms) = preSolution 19 | Debug|Any CPU = Debug|Any CPU 20 | Release|Any CPU = Release|Any CPU 21 | EndGlobalSection 22 | GlobalSection(ProjectConfigurationPlatforms) = postSolution 23 | {61BE7A3D-AC66-434B-8A77-4F3466398CBC}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 24 | {61BE7A3D-AC66-434B-8A77-4F3466398CBC}.Debug|Any CPU.Build.0 = Debug|Any CPU 25 | {61BE7A3D-AC66-434B-8A77-4F3466398CBC}.Release|Any CPU.ActiveCfg = Release|Any CPU 26 | {61BE7A3D-AC66-434B-8A77-4F3466398CBC}.Release|Any CPU.Build.0 = Release|Any CPU 27 | {EAA37D17-2AF6-447E-A568-97F061C9D1D7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 28 | {EAA37D17-2AF6-447E-A568-97F061C9D1D7}.Debug|Any CPU.Build.0 = Debug|Any CPU 29 | {EAA37D17-2AF6-447E-A568-97F061C9D1D7}.Release|Any CPU.ActiveCfg = Release|Any CPU 30 | {EAA37D17-2AF6-447E-A568-97F061C9D1D7}.Release|Any CPU.Build.0 = Release|Any CPU 31 | {28550F43-E353-44F0-B145-1688B7C85569}.Debug|Any CPU.ActiveCfg = Debug|Any CPU 32 | {28550F43-E353-44F0-B145-1688B7C85569}.Debug|Any CPU.Build.0 = Debug|Any CPU 33 | {28550F43-E353-44F0-B145-1688B7C85569}.Release|Any CPU.ActiveCfg = Release|Any CPU 34 | {28550F43-E353-44F0-B145-1688B7C85569}.Release|Any CPU.Build.0 = Release|Any CPU 35 | EndGlobalSection 36 | GlobalSection(SolutionProperties) = preSolution 37 | HideSolutionNode = FALSE 38 | EndGlobalSection 39 | EndGlobal 40 | -------------------------------------------------------------------------------- /AsyncCollections/.gitignore: -------------------------------------------------------------------------------- 1 | /bin/ 2 | /obj/ 3 | -------------------------------------------------------------------------------- /AsyncCollections/AnyResult.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HellBrick.Collections 8 | { 9 | /// 10 | /// Represents an item retrieved from one of the asynchronous collections. 11 | /// 12 | public struct AnyResult : IEquatable> 13 | { 14 | public AnyResult( T value, int collectionIndex ) 15 | { 16 | Value = value; 17 | CollectionIndex = collectionIndex; 18 | } 19 | 20 | /// 21 | /// Gets the item retrieved from a collection. 22 | /// 23 | public T Value { get; } 24 | 25 | /// 26 | /// Gets the index of the collection the item was retrieved from. 27 | /// 28 | public int CollectionIndex { get; } 29 | 30 | public override int GetHashCode() 31 | { 32 | unchecked 33 | { 34 | const int prime = -1521134295; 35 | int hash = 12345701; 36 | hash = hash * prime + EqualityComparer.Default.GetHashCode( Value ); 37 | hash = hash * prime + EqualityComparer.Default.GetHashCode( CollectionIndex ); 38 | return hash; 39 | } 40 | } 41 | 42 | public bool Equals( AnyResult other ) => EqualityComparer.Default.Equals( Value, other.Value ) && EqualityComparer.Default.Equals( CollectionIndex, other.CollectionIndex ); 43 | public override bool Equals( object obj ) => obj is AnyResult && Equals( (AnyResult) obj ); 44 | 45 | public static bool operator ==( AnyResult x, AnyResult y ) => x.Equals( y ); 46 | public static bool operator !=( AnyResult x, AnyResult y ) => !x.Equals( y ); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /AsyncCollections/AsyncBatchQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace HellBrick.Collections 9 | { 10 | /// 11 | /// Represents a thread-safe collection that groups the items added to it into batches and allows consuming them asynchronously. 12 | /// 13 | /// The type of the items contained in the collection. 14 | public class AsyncBatchQueue : IAsyncBatchCollection 15 | { 16 | private volatile Batch _currentBatch; 17 | private readonly AsyncQueue> _batchQueue = new AsyncQueue>(); 18 | 19 | /// 20 | /// Initializes a new instance of that produces batches of a specified size. 21 | /// 22 | /// Amount of the items contained in an output batch. 23 | public AsyncBatchQueue( int batchSize ) 24 | { 25 | if ( batchSize <= 0 ) 26 | throw new ArgumentOutOfRangeException( "batchSize", batchSize, "Batch size must be a positive integer." ); 27 | 28 | BatchSize = batchSize; 29 | _currentBatch = new Batch( this ); 30 | } 31 | 32 | /// 33 | /// Gets amount of items contained in an output batch. 34 | /// 35 | public int BatchSize { get; } 36 | 37 | /// 38 | /// Gets the number of flushed batches currently available for consuming. 39 | /// 40 | public int Count => _batchQueue.Count; 41 | 42 | /// 43 | /// Adds an item to the collection. Flushes the new batch to be available for consuming if amount of the pending items has reached . 44 | /// 45 | /// 46 | public void Add( T item ) 47 | { 48 | SpinWait spin = new SpinWait(); 49 | 50 | while ( !_currentBatch.TryAdd( item ) ) 51 | spin.SpinOnce(); 52 | } 53 | 54 | /// 55 | /// Removes and returns a batch from the collection in an asynchronous manner. 56 | /// 57 | public ValueTask> TakeAsync() => TakeAsync( CancellationToken.None ); 58 | 59 | /// 60 | /// Removes and returns a batch from the collection in an asynchronous manner. 61 | /// 62 | public ValueTask> TakeAsync( CancellationToken cancellationToken ) => _batchQueue.TakeAsync( cancellationToken ); 63 | 64 | /// 65 | /// Forces a new batch to be created and made available for consuming even if amount of the pending items has not reached yet. 66 | /// Does nothing if there are no pending items to flush. 67 | /// 68 | public void Flush() 69 | { 70 | SpinWait spin = new SpinWait(); 71 | while ( !_currentBatch.TryFlush() ) 72 | spin.SpinOnce(); 73 | } 74 | 75 | public IEnumerator> GetEnumerator() => _batchQueue.GetEnumerator(); 76 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); 77 | 78 | private class Batch : IReadOnlyList 79 | { 80 | private readonly AsyncBatchQueue _queue; 81 | private readonly T[] _items; 82 | private readonly bool[] _finalizationFlags; 83 | private int _lastReservationIndex = -1; 84 | private int _count = -1; 85 | 86 | public Batch( AsyncBatchQueue queue ) 87 | { 88 | _queue = queue; 89 | _items = new T[ _queue.BatchSize ]; 90 | _finalizationFlags = new bool[ _queue.BatchSize ]; 91 | } 92 | 93 | public bool TryAdd( T item ) 94 | { 95 | int index = Interlocked.Increment( ref _lastReservationIndex ); 96 | 97 | // The following is true if someone has beaten us to the last slot and we have to wait until the next batch comes along. 98 | if ( index >= _queue.BatchSize ) 99 | return false; 100 | 101 | // The following is true if we've taken the last slot, which means we're obligated to flush the current batch and create a new one. 102 | if ( index == _queue.BatchSize - 1 ) 103 | FlushInternal( _queue.BatchSize ); 104 | 105 | // The full fence prevents setting finalization flag before the actual item value is written. 106 | _items[ index ] = item; 107 | Interlocked.MemoryBarrier(); 108 | _finalizationFlags[ index ] = true; 109 | 110 | return true; 111 | } 112 | 113 | public bool TryFlush() 114 | { 115 | int expectedPreviousReservation = Volatile.Read( ref _lastReservationIndex ); 116 | 117 | // We don't flush if the batch doesn't have any items or if another thread is about to flush it. 118 | // However, we report success to avoid unnecessary spinning. 119 | if ( expectedPreviousReservation < 0 || expectedPreviousReservation >= _queue.BatchSize - 1 ) 120 | return true; 121 | 122 | int previousReservation = Interlocked.CompareExchange( ref _lastReservationIndex, _queue.BatchSize, expectedPreviousReservation ); 123 | 124 | // Flush reservation has succeeded. 125 | if ( expectedPreviousReservation == previousReservation ) 126 | { 127 | FlushInternal( previousReservation + 1 ); 128 | return true; 129 | } 130 | 131 | // The following is true if someone has completed the batch by the time we tried to flush it. 132 | // Therefore the batch will be flushed anyway even if we don't do anything. 133 | // The opposite means someone has slipped in an update and we have to spin. 134 | return previousReservation >= _queue.BatchSize; 135 | } 136 | 137 | private void FlushInternal( int count ) 138 | { 139 | _count = count; 140 | _queue._currentBatch = new Batch( _queue ); 141 | 142 | // The full fence ensures that the current batch will never be added to the queue before _count is set. 143 | Interlocked.MemoryBarrier(); 144 | 145 | _queue._batchQueue.Add( this ); 146 | } 147 | 148 | private T GetItemWithoutValidation( int index ) 149 | { 150 | SpinWait spin = new SpinWait(); 151 | while ( !_finalizationFlags[ index ] ) 152 | { 153 | spin.SpinOnce(); 154 | 155 | // The full fence prevents caching any part of _finalizationFlags[ index ] expression. 156 | Interlocked.MemoryBarrier(); 157 | } 158 | 159 | // The full fence prevents reading item value before finalization flag is set. 160 | Interlocked.MemoryBarrier(); 161 | return _items[ index ]; 162 | } 163 | 164 | public T this[ int index ] 165 | { 166 | get 167 | { 168 | if ( index >= Count ) 169 | throw new IndexOutOfRangeException(); 170 | 171 | return GetItemWithoutValidation( index ); 172 | } 173 | } 174 | 175 | public int Count => _count; 176 | 177 | public IEnumerator GetEnumerator() 178 | { 179 | for ( int i = 0; i < Count; i++ ) 180 | yield return GetItemWithoutValidation( i ); 181 | } 182 | 183 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => GetEnumerator(); 184 | } 185 | } 186 | } -------------------------------------------------------------------------------- /AsyncCollections/AsyncBoundedPriorityQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | 10 | namespace HellBrick.Collections 11 | { 12 | /// 13 | /// Represents a thread-safe queue with a bounded number of priority levels. 14 | /// 15 | /// The type of the items contained in the queue. 16 | public class AsyncBoundedPriorityQueue : AsyncCollection>, IAsyncCollection 17 | { 18 | private readonly Func _priorityResolver; 19 | 20 | /// 21 | /// Initializes a new instance with a specified number of priority levels. 22 | /// The items will be inserted at the lowest priority by default. 23 | /// 24 | /// An amount of priority levels to support. 25 | public AsyncBoundedPriorityQueue( int priorityLevels ) 26 | : this( priorityLevels, _ => priorityLevels - 1 ) 27 | { 28 | } 29 | 30 | /// 31 | /// Initializes a new instance with a specified number of priority levels and a specified priority resolver. 32 | /// 33 | /// An amount of priority levels to support. 34 | /// The delegate to use to determine the default item priority. 35 | /// Must return an integer between 0 (top priority) and - 1 (low priority). 36 | /// 37 | public AsyncBoundedPriorityQueue( int priorityLevels, Func priorityResolver ) 38 | : base( new ConcurrentBoundedPriorityQueue( priorityLevels ) ) 39 | { 40 | PriorityLevels = priorityLevels; 41 | _priorityResolver = priorityResolver; 42 | } 43 | 44 | /// 45 | /// Gets the amount of priority levels the collection supports. 46 | /// 47 | public int PriorityLevels { get; } 48 | 49 | /// 50 | /// Adds an item to the collection at the highest priority. 51 | /// 52 | /// The item to add to the collection. 53 | public void AddTopPriority( T item ) => Add( item, 0 ); 54 | 55 | /// 56 | /// Adds an item to the collection at the lowest priority. 57 | /// 58 | /// The item to add to the collection. 59 | public void AddLowPriority( T item ) => Add( item, PriorityLevels - 1 ); 60 | 61 | /// 62 | /// Adds an item to the collection at a specified priority. 63 | /// 64 | /// The item to add to the collection. 65 | /// The priority of the item, with 0 being the top priority. 66 | public void Add( T item, int priority ) 67 | { 68 | if ( priority < 0 || priority > PriorityLevels ) 69 | throw new ArgumentOutOfRangeException( nameof( priority ), priority, $"Priority can't be less than 0 or bigger than {PriorityLevels - 1}." ); 70 | 71 | Add( new PrioritizedItem( item, priority ) ); 72 | } 73 | 74 | /// 75 | /// Adds an item to the collection at default priority. 76 | /// 77 | /// The item to add to the collection. 78 | public void Add( T item ) => Add( item, _priorityResolver( item ) ); 79 | 80 | /// 81 | /// Removes and returns an item with the highest priority from the collection in an asynchronous manner. 82 | /// 83 | public ValueTask> TakeAsync() => TakeAsync( CancellationToken.None ); 84 | 85 | /// 86 | /// Removes and returns an item with the highest priority from the collection in an asynchronous manner. 87 | /// 88 | ValueTask IAsyncCollection.TakeAsync( System.Threading.CancellationToken cancellationToken ) 89 | { 90 | ValueTask> prioritizedItemTask = TakeAsync( cancellationToken ); 91 | return prioritizedItemTask.IsCompletedSuccessfully ? new ValueTask( prioritizedItemTask.Result.Item ) : new ValueTask( UnwrapAsync( prioritizedItemTask ) ); 92 | } 93 | 94 | private async Task UnwrapAsync( ValueTask> prioritizedItemTask ) 95 | { 96 | PrioritizedItem result = await prioritizedItemTask.ConfigureAwait( false ); 97 | return result.Item; 98 | } 99 | 100 | IEnumerator IEnumerable.GetEnumerator() => ( this as IEnumerable> ).Select( prioritizedItem => prioritizedItem.Item ).GetEnumerator(); 101 | 102 | private class ConcurrentBoundedPriorityQueue : IProducerConsumerCollection> 103 | { 104 | private readonly ConcurrentQueue[] _itemQueues; 105 | 106 | public ConcurrentBoundedPriorityQueue( int priorityLevels ) 107 | { 108 | if ( priorityLevels < 0 ) 109 | throw new ArgumentOutOfRangeException( nameof( priorityLevels ), priorityLevels, "Amount of priority levels can't be less than 0." ); 110 | 111 | _itemQueues = Enumerable.Range( 0, priorityLevels ).Select( _ => new ConcurrentQueue() ).ToArray(); 112 | } 113 | 114 | public int Count => _itemQueues.Sum( q => q.Count ); 115 | 116 | public bool TryAdd( PrioritizedItem item ) 117 | { 118 | _itemQueues[ item.Priority ].Enqueue( item.Item ); 119 | return true; 120 | } 121 | 122 | public bool TryTake( out PrioritizedItem item ) 123 | { 124 | for ( int priority = 0; priority < _itemQueues.Length; priority++ ) 125 | { 126 | T itemValue; 127 | if ( _itemQueues[ priority ].TryDequeue( out itemValue ) ) 128 | { 129 | item = new PrioritizedItem( itemValue, priority ); 130 | return true; 131 | } 132 | } 133 | 134 | item = default( PrioritizedItem ); 135 | return false; 136 | } 137 | 138 | public IEnumerator> GetEnumerator() 139 | => _itemQueues 140 | .SelectMany( ( queue, index ) => queue.Select( item => new PrioritizedItem( item, index ) ) ) 141 | .GetEnumerator(); 142 | 143 | IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); 144 | bool ICollection.IsSynchronized => false; 145 | object ICollection.SyncRoot => null; 146 | 147 | void ICollection.CopyTo( Array array, int index ) 148 | { 149 | throw new NotSupportedException(); 150 | } 151 | 152 | void IProducerConsumerCollection>.CopyTo( PrioritizedItem[] array, int index ) 153 | { 154 | throw new NotSupportedException(); 155 | } 156 | 157 | PrioritizedItem[] IProducerConsumerCollection>.ToArray() 158 | { 159 | throw new NotSupportedException(); 160 | } 161 | } 162 | } 163 | } 164 | -------------------------------------------------------------------------------- /AsyncCollections/AsyncCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Diagnostics; 5 | using System.Linq; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Threading.Tasks; 9 | using HellBrick.Collections.Internal; 10 | 11 | namespace HellBrick.Collections 12 | { 13 | /// 14 | /// Represents a thread-safe collection that allows asynchronous consuming. 15 | /// 16 | /// The type of the items contained in the collection. 17 | public class AsyncCollection : IAsyncCollection 18 | { 19 | private readonly IProducerConsumerCollection _itemQueue; 20 | private readonly ConcurrentQueue> _awaiterQueue = new ConcurrentQueue>(); 21 | 22 | // _queueBalance < 0 means there are free awaiters and not enough items. 23 | // _queueBalance > 0 means the opposite is true. 24 | private long _queueBalance = 0; 25 | 26 | /// 27 | /// Initializes a new instance of with a specified as an underlying item storage. 28 | /// 29 | /// The collection to use as an underlying item storage. MUST NOT be accessed elsewhere. 30 | public AsyncCollection( IProducerConsumerCollection itemQueue ) 31 | { 32 | _itemQueue = itemQueue; 33 | _queueBalance = _itemQueue.Count; 34 | } 35 | 36 | public int Count => _itemQueue.Count; 37 | 38 | /// 39 | /// Gets an amount of pending item requests. 40 | /// 41 | public int AwaiterCount => _awaiterQueue.Count; 42 | 43 | /// 44 | /// Adds an item to the collection. 45 | /// 46 | /// The item to add to the collection. 47 | public void Add( T item ) 48 | { 49 | while ( !TryAdd( item ) ) ; 50 | } 51 | 52 | /// 53 | /// Tries to add an item to the collection. 54 | /// May fail if an awaiter that's supposed to receive the item is cancelled. If this is the case, the TryAdd() method must be called again. 55 | /// 56 | /// The item to add to the collection. 57 | /// True if the item was added to the collection; false if the awaiter was cancelled and the operation must be retried. 58 | private bool TryAdd( T item ) 59 | { 60 | long balanceAfterCurrentItem = Interlocked.Increment( ref _queueBalance ); 61 | SpinWait spin = new SpinWait(); 62 | 63 | if ( balanceAfterCurrentItem > 0 ) 64 | { 65 | // Items are dominating, so we can safely add a new item to the queue. 66 | while ( !_itemQueue.TryAdd( item ) ) 67 | spin.SpinOnce(); 68 | 69 | return true; 70 | } 71 | else 72 | { 73 | // There's at least one awaiter available or being added as we're speaking, so we're giving the item to it. 74 | 75 | IAwaiter awaiter; 76 | 77 | while ( !_awaiterQueue.TryDequeue( out awaiter ) ) 78 | spin.SpinOnce(); 79 | 80 | // Returns false if the cancellation occurred earlier. 81 | return awaiter.TrySetResult( item ); 82 | } 83 | } 84 | 85 | /// 86 | /// Removes and returns an item from the collection in an asynchronous manner. 87 | /// 88 | public ValueTask TakeAsync( CancellationToken cancellationToken ) 89 | => cancellationToken.IsCancellationRequested 90 | ? CanceledValueTask.Value 91 | : TakeAsync( new CompletionSourceAwaiterFactory( cancellationToken ) ); 92 | 93 | private ValueTask TakeAsync( TAwaiterFactory awaiterFactory ) where TAwaiterFactory : IAwaiterFactory 94 | { 95 | long balanceAfterCurrentAwaiter = Interlocked.Decrement( ref _queueBalance ); 96 | 97 | if ( balanceAfterCurrentAwaiter < 0 ) 98 | { 99 | // Awaiters are dominating, so we can safely add a new awaiter to the queue. 100 | IAwaiter awaiter = awaiterFactory.CreateAwaiter(); 101 | _awaiterQueue.Enqueue( awaiter ); 102 | return awaiter.Task; 103 | } 104 | else 105 | { 106 | // There's at least one item available or being added, so we're returning it directly. 107 | 108 | T item; 109 | SpinWait spin = new SpinWait(); 110 | 111 | while ( !_itemQueue.TryTake( out item ) ) 112 | spin.SpinOnce(); 113 | 114 | return new ValueTask( item ); 115 | } 116 | } 117 | 118 | public override string ToString() => $"Count = {Count}, Awaiters = {AwaiterCount}"; 119 | 120 | public IEnumerator GetEnumerator() => _itemQueue.GetEnumerator(); 121 | System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() => _itemQueue.GetEnumerator(); 122 | 123 | #region Static 124 | 125 | internal const int TakeFromAnyMaxCollections = BitArray32.BitCapacity; 126 | 127 | /// 128 | /// Removes and returns an item from one of the specified collections in an asynchronous manner. 129 | /// 130 | public static ValueTask> TakeFromAnyAsync( AsyncCollection[] collections ) => TakeFromAnyAsync( collections, CancellationToken.None ); 131 | 132 | /// 133 | /// Removes and returns an item from one of the specified collections in an asynchronous manner. 134 | /// 135 | public static ValueTask> TakeFromAnyAsync( AsyncCollection[] collections, CancellationToken cancellationToken ) 136 | { 137 | if ( collections == null ) 138 | throw new ArgumentNullException( "collections" ); 139 | 140 | if ( collections.Length <= 0 || collections.Length > TakeFromAnyMaxCollections ) 141 | throw new ArgumentException( String.Format( "The collection array can't contain less than 1 or more than {0} collections.", TakeFromAnyMaxCollections ), "collections" ); 142 | 143 | if ( cancellationToken.IsCancellationRequested ) 144 | return CanceledValueTask>.Value; 145 | 146 | ExclusiveCompletionSourceGroup exclusiveSources = new ExclusiveCompletionSourceGroup(); 147 | 148 | // Fast route: we attempt to take from the top-priority queues that have any items. 149 | // If the fast route succeeds, we avoid allocating and queueing a bunch of awaiters. 150 | for ( int i = 0; i < collections.Length; i++ ) 151 | { 152 | if ( collections[ i ].Count > 0 ) 153 | { 154 | AnyResult? result = TryTakeFast( exclusiveSources, collections[ i ], i ); 155 | if ( result.HasValue ) 156 | return new ValueTask>( result.Value ); 157 | } 158 | } 159 | 160 | // No luck during the fast route; just queue the rest of awaiters. 161 | for ( int i = 0; i < collections.Length; i++ ) 162 | { 163 | AnyResult? result = TryTakeFast( exclusiveSources, collections[ i ], i ); 164 | if ( result.HasValue ) 165 | return new ValueTask>( result.Value ); 166 | } 167 | 168 | // None of the collections had any items. The order doesn't matter anymore, it's time to start the competition. 169 | exclusiveSources.UnlockCompetition( cancellationToken ); 170 | return new ValueTask>( exclusiveSources.Task ); 171 | } 172 | 173 | private static AnyResult? TryTakeFast( ExclusiveCompletionSourceGroup exclusiveSources, AsyncCollection collection, int index ) 174 | { 175 | // This can happen if the awaiter has already been created during the fast route. 176 | if ( exclusiveSources.IsAwaiterCreated( index ) ) 177 | return null; 178 | 179 | ValueTask collectionTask = collection.TakeAsync( exclusiveSources.CreateAwaiterFactory( index ) ); 180 | 181 | // One of the collections already had an item and returned it directly 182 | if ( collectionTask != null && collectionTask.IsCompleted ) 183 | { 184 | exclusiveSources.MarkAsResolved(); 185 | return new AnyResult( collectionTask.Result, index ); 186 | } 187 | else 188 | return null; 189 | } 190 | 191 | #endregion 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /AsyncCollections/AsyncCollections.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Release 6 | AnyCPU 7 | {61BE7A3D-AC66-434B-8A77-4F3466398CBC} 8 | Library 9 | Properties 10 | HellBrick.Collections 11 | AsyncCollections 12 | v4.6 13 | 512 14 | 15 | {786C830F-07A1-408B-BD7F-6EE04809D6DB};{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC} 16 | Profile151 17 | 12.0 18 | 19 | 20 | 21 | 22 | 12.0 23 | publish\ 24 | true 25 | Disk 26 | false 27 | Foreground 28 | 7 29 | Days 30 | false 31 | false 32 | true 33 | 0 34 | 1.0.0.%2a 35 | false 36 | false 37 | true 38 | 39 | 40 | true 41 | full 42 | false 43 | bin\Debug\ 44 | DEBUG;TRACE 45 | prompt 46 | 4 47 | 48 | 49 | pdbonly 50 | true 51 | bin\Release\ 52 | TRACE 53 | prompt 54 | 4 55 | 56 | 57 | OnBuildSuccess 58 | 59 | 60 | 61 | 62 | 63 | 64 | 65 | 66 | 67 | 68 | 69 | 70 | 71 | 72 | 73 | 74 | 75 | 76 | 77 | 78 | 79 | 80 | 81 | 82 | 83 | 84 | 85 | 86 | 87 | 88 | 89 | 90 | 91 | 92 | 93 | 94 | 95 | 96 | 97 | False 98 | .NET Framework 3.5 SP1 Client Profile 99 | false 100 | 101 | 102 | False 103 | .NET Framework 3.5 SP1 104 | false 105 | 106 | 107 | 108 | 109 | 110 | 111 | 112 | 113 | 114 | 115 | 116 | 117 | 118 | 119 | 120 | 121 | 122 | 123 | 124 | 125 | 126 | 127 | 128 | -------------------------------------------------------------------------------- /AsyncCollections/AsyncQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Concurrent; 4 | using System.Collections.Generic; 5 | using System.Diagnostics; 6 | using System.Linq; 7 | using System.Text; 8 | using System.Threading; 9 | using System.Threading.Tasks; 10 | using HellBrick.Collections.Internal; 11 | 12 | namespace HellBrick.Collections 13 | { 14 | /// 15 | /// Represents a thread-safe queue that allows asynchronous consuming. 16 | /// 17 | /// The type of the items contained in the queue. 18 | public class AsyncQueue : IAsyncCollection 19 | { 20 | internal const int SegmentSize = 32; 21 | 22 | private Segment _itemTail; 23 | private Segment _awaiterTail; 24 | 25 | /// 26 | /// This actually points to either or , depending on which one lags behind. 27 | /// The only reason this field exists is to simplify enumerating segments for things like , or . 28 | /// 29 | private Segment _head; 30 | 31 | /// 32 | /// The value is positive if there are any active enumerators and negative if any segments are being transferred to the pool. 33 | /// 34 | private int _enumerationPoolingBalance = 0; 35 | 36 | private Segment _segmentPoolHead = null; 37 | 38 | /// 39 | /// Initializes a new empty instance of . 40 | /// 41 | public AsyncQueue() 42 | { 43 | Segment firstSegment = new Segment( this ) { SegmentID = 0 }; 44 | _itemTail = firstSegment; 45 | _awaiterTail = firstSegment; 46 | _head = firstSegment; 47 | } 48 | 49 | /// 50 | /// Initializes a new instance of that contains elements copied from a specified collection. 51 | /// 52 | /// The collection whose elements are copied to the new queue. 53 | public AsyncQueue( IEnumerable collection ) 54 | : this() 55 | { 56 | foreach ( T item in collection ) 57 | Add( item ); 58 | } 59 | 60 | public int AwaiterCount => ComputeCount( Volatile.Read( ref _awaiterTail ), Volatile.Read( ref _itemTail ), s => s.AwaiterCount ); 61 | public int Count => ComputeCount( Volatile.Read( ref _itemTail ), Volatile.Read( ref _awaiterTail ), s => s.ItemCount ); 62 | 63 | private int ComputeCount( Segment myTail, Segment otherTail, Func countExtractor ) 64 | { 65 | if ( myTail.SegmentID < otherTail.SegmentID ) 66 | return 0; 67 | 68 | if ( myTail.SegmentID == otherTail.SegmentID ) 69 | return countExtractor( myTail ); 70 | 71 | int count = countExtractor( myTail ) + countExtractor( otherTail ); 72 | long fullMiddleSegmentCount = myTail.SegmentID - otherTail.SegmentID - 1; 73 | if ( fullMiddleSegmentCount > 0 ) 74 | count += SegmentSize * (int) fullMiddleSegmentCount; 75 | 76 | return count; 77 | } 78 | 79 | public void Add( T item ) 80 | { 81 | SpinWait spin = new SpinWait(); 82 | while ( !Volatile.Read( ref _itemTail ).TryAdd( item ) ) 83 | spin.SpinOnce(); 84 | } 85 | 86 | public ValueTask TakeAsync( CancellationToken cancellationToken ) 87 | => cancellationToken.IsCancellationRequested 88 | ? CanceledValueTask.Value 89 | : TakeWithoutValidationAsync( cancellationToken ); 90 | 91 | private ValueTask TakeWithoutValidationAsync( CancellationToken cancellationToken ) 92 | { 93 | SpinWait spin = new SpinWait(); 94 | 95 | while ( true ) 96 | { 97 | ValueTask? result = Volatile.Read( ref _awaiterTail ).TryTakeAsync( cancellationToken ); 98 | if ( result != null ) 99 | return result.Value; 100 | 101 | spin.SpinOnce(); 102 | } 103 | } 104 | 105 | public Enumerator GetEnumerator() 106 | { 107 | SpinWait spin = new SpinWait(); 108 | 109 | while ( true ) 110 | { 111 | int oldBalance = Volatile.Read( ref _enumerationPoolingBalance ); 112 | if ( oldBalance >= 0 && Interlocked.CompareExchange( ref _enumerationPoolingBalance, oldBalance + 1, oldBalance ) == oldBalance ) 113 | break; 114 | 115 | spin.SpinOnce(); 116 | } 117 | 118 | return new Enumerator( this ); 119 | } 120 | 121 | IEnumerator IEnumerable.GetEnumerator() => new BoxedEnumerator( GetEnumerator() ); 122 | IEnumerator IEnumerable.GetEnumerator() => ( this as IEnumerable ).GetEnumerator(); 123 | 124 | public struct Enumerator : IEnumerator 125 | { 126 | private SelectManyStructEnumererator _innerEnumerator; 127 | private readonly AsyncQueue _queue; 128 | 129 | public Enumerator( AsyncQueue queue ) 130 | { 131 | _queue = queue; 132 | _innerEnumerator = new SelectManyStructEnumererator( new SegmentEnumerator( queue ), segment => segment.GetEnumerator() ); 133 | } 134 | 135 | public T Current => _innerEnumerator.Current; 136 | object IEnumerator.Current => Current; 137 | 138 | public bool MoveNext() => _innerEnumerator.MoveNext(); 139 | public void Reset() => _innerEnumerator.Reset(); 140 | 141 | public void Dispose() 142 | { 143 | _innerEnumerator.Dispose(); 144 | Interlocked.Decrement( ref _queue._enumerationPoolingBalance ); 145 | } 146 | } 147 | 148 | private struct SegmentEnumerator : IEnumerator 149 | { 150 | private readonly AsyncQueue _queue; 151 | private bool _readFirstSegment; 152 | 153 | public SegmentEnumerator( AsyncQueue queue ) 154 | { 155 | _queue = queue; 156 | Current = default( Segment ); 157 | _readFirstSegment = false; 158 | } 159 | 160 | public Segment Current { get; private set; } 161 | object IEnumerator.Current => Current; 162 | 163 | public bool MoveNext() 164 | { 165 | if ( !_readFirstSegment ) 166 | { 167 | Current = Volatile.Read( ref _queue._head ); 168 | _readFirstSegment = true; 169 | return true; 170 | } 171 | 172 | Current = Current.VolatileNext; 173 | return Current != null; 174 | } 175 | 176 | public void Dispose() 177 | { 178 | } 179 | 180 | public void Reset() 181 | { 182 | } 183 | } 184 | 185 | private class Segment : IEnumerable 186 | { 187 | private readonly T[] _items = new T[ SegmentSize ]; 188 | private readonly IAwaiter[] _awaiters = new IAwaiter[ SegmentSize ]; 189 | private readonly int[] _slotStates = new int[ SegmentSize ]; 190 | 191 | private readonly AsyncQueue _queue; 192 | private long _segmentID; 193 | 194 | private int _awaiterIndex = -1; 195 | private int _itemIndex = -1; 196 | private Segment _next = null; 197 | 198 | private Segment _nextPooledSegment = null; 199 | 200 | public Segment( AsyncQueue queue ) 201 | { 202 | _queue = queue; 203 | } 204 | 205 | public Segment VolatileNext => Volatile.Read( ref _next ); 206 | 207 | public long SegmentID 208 | { 209 | get { return Volatile.Read( ref _segmentID ); } 210 | set { Volatile.Write( ref _segmentID, value ); } 211 | } 212 | 213 | public int ItemCount => Math.Max( 0, ItemAwaiterBalance ); 214 | public int AwaiterCount => Math.Max( 0, -ItemAwaiterBalance ); 215 | 216 | private int ItemAwaiterBalance => SlotReferenceToCount( ref _itemIndex ) - SlotReferenceToCount( ref _awaiterIndex ); 217 | private int SlotReferenceToCount( ref int slotReference ) => Math.Min( SegmentSize, Volatile.Read( ref slotReference ) + 1 ); 218 | 219 | public bool TryAdd( T item ) 220 | => TryAdd( item, Interlocked.Increment( ref _itemIndex ) ); 221 | 222 | private bool TryAdd( T item, int slot ) 223 | => slot < SegmentSize 224 | && TryAddWithoutValidation( item, slot ); 225 | 226 | private bool TryAddWithoutValidation( T item, int slot ) 227 | { 228 | _items[ slot ] = item; 229 | bool wonSlot = Interlocked.CompareExchange( ref _slotStates[ slot ], SlotState.HasItem, SlotState.None ) == SlotState.None; 230 | 231 | /// 1. If we have won the slot, the item is considered successfully added. 232 | /// 2. Otherwise, it's up to the result of . 233 | /// Awaiter could have been canceled by now, and if it has, we should return false to insert item again into another slot. 234 | /// We also can't blindly read awaiter from the slot, because captures slot *before* filling in the awaiter. 235 | /// So we have to spin until it is available. 236 | /// And regardless of the awaiter state, we mark the slot as finished because both item and awaiter have visited it. 237 | bool success = wonSlot || TrySetAwaiterResultAndClearSlot( item, slot ); 238 | 239 | HandleLastSlotCapture( slot, wonSlot, ref _queue._itemTail ); 240 | return success; 241 | } 242 | 243 | private bool TrySetAwaiterResultAndClearSlot( T item, int slot ) 244 | { 245 | bool success = SpinUntilAwaiterIsReady( slot ).TrySetResult( item ); 246 | ClearSlot( slot ); 247 | return success; 248 | } 249 | 250 | private IAwaiter SpinUntilAwaiterIsReady( int slot ) 251 | { 252 | SpinWait spin = new SpinWait(); 253 | while ( true ) 254 | { 255 | IAwaiter awaiter = Volatile.Read( ref _awaiters[ slot ] ); 256 | if ( awaiter != null ) 257 | return awaiter; 258 | 259 | spin.SpinOnce(); 260 | } 261 | } 262 | 263 | public ValueTask? TryTakeAsync( CancellationToken cancellationToken ) 264 | => TryTakeAsync( cancellationToken, Interlocked.Increment( ref _awaiterIndex ) ); 265 | 266 | private ValueTask? TryTakeAsync( CancellationToken cancellationToken, int slot ) 267 | => slot < SegmentSize 268 | ? TryTakeWithoutValidationAsync( cancellationToken, slot ) 269 | : (ValueTask?) null; 270 | 271 | private ValueTask TryTakeWithoutValidationAsync( CancellationToken cancellationToken, int slot ) 272 | { 273 | ValueTask result; 274 | 275 | /// The order here differs from what does: we capture the slot *before* inserting an awaiter. 276 | /// We do it to avoid allocating an awaiter / registering the cancellation that we're not gonna need in case we lose. 277 | /// This means can see the default awaiter value, but it is easily solved by spinning until the awaiter is assigned. 278 | bool wonSlot = Interlocked.CompareExchange( ref _slotStates[ slot ], SlotState.HasAwaiter, SlotState.None ) == SlotState.None; 279 | if ( wonSlot ) 280 | { 281 | IAwaiter awaiter = new CompletionSourceAwaiterFactory( cancellationToken ).CreateAwaiter(); 282 | Volatile.Write( ref _awaiters[ slot ], awaiter ); 283 | result = awaiter.Task; 284 | } 285 | else 286 | { 287 | result = new ValueTask( _items[ slot ] ); 288 | ClearSlot( slot ); 289 | } 290 | 291 | HandleLastSlotCapture( slot, wonSlot, ref _queue._awaiterTail ); 292 | return result; 293 | } 294 | 295 | private void ClearSlot( int slot ) 296 | { 297 | Volatile.Write( ref _slotStates[ slot ], SlotState.Cleared ); 298 | Volatile.Write( ref _awaiters[ slot ], null ); 299 | _items[ slot ] = default( T ); 300 | } 301 | 302 | /// 303 | /// Here comes the tricky part. 304 | /// 0. We only care if we've captured exactly the last slot, so only one thread performs the segment maintenance. 305 | /// 1. Whether the slot is lost or won, the next time the same kind of item is inserted, there's no point in looking for a slot at the current segment. 306 | /// So we have to advance or , depending on the kind of item we're working on right now. 307 | /// 2. The last slot is captured twice: by an item and by an awaiter. We obviously should to grow a segment only once, so only the winner does it. 308 | /// 3. If we've lost the last slot, it's still possible the next segment is not grown yet, so we have to spin. 309 | /// 4. If we've lost the last slot, it means we're done with the current segment: all items and all awaiters have annihilated each other. 310 | /// and are 0 now, so the segment can't contribute to or . 311 | /// So we lose the reference to it by advancing . 312 | /// 5. If we've lost the last slot, we pool it to be reused later. 313 | /// 314 | /// Either or , whichever we're working on right now. 315 | private void HandleLastSlotCapture( int slot, bool wonSlot, ref Segment tailReference ) 316 | { 317 | if ( !IsLastSlot( slot ) ) 318 | return; 319 | 320 | Segment nextSegment = wonSlot ? GrowSegment() : SpinUntilNextSegmentIsReady(); 321 | Volatile.Write( ref tailReference, nextSegment ); 322 | 323 | if ( wonSlot ) 324 | return; 325 | 326 | Volatile.Write( ref _queue._head, nextSegment ); 327 | TryPoolSegment(); 328 | } 329 | 330 | private void TryPoolSegment() 331 | { 332 | if ( !TryDecreaseBalance() ) 333 | return; 334 | 335 | /// We reset so it could be GC-ed if it doesn't make it to the pool. 336 | /// It's safe to do so because: 337 | /// 1. guarantees that the whole queue is not being enumerated right now. 338 | /// 2. By this time is already rewritten so future enumerators can't possibly reference the current segment. 339 | /// The rest of the clean-up is *NOT* safe to do here, see for details. 340 | Volatile.Write( ref _next, null ); 341 | PushToPool(); 342 | Interlocked.Increment( ref _queue._enumerationPoolingBalance ); 343 | } 344 | 345 | private bool TryDecreaseBalance() 346 | { 347 | SpinWait spin = new SpinWait(); 348 | while ( true ) 349 | { 350 | int enumeratorPoolBalance = Volatile.Read( ref _queue._enumerationPoolingBalance ); 351 | 352 | // If the balance is positive, we have some active enumerators and it's dangerous to pool the segment right now. 353 | // We can't spin until the balance is restored either, because we have no guarantee that enumerators will be disposed soon (or will be disposed at all). 354 | // So we have no choice but to give up on pooling the segment. 355 | if ( enumeratorPoolBalance > 0 ) 356 | return false; 357 | 358 | if ( Interlocked.CompareExchange( ref _queue._enumerationPoolingBalance, enumeratorPoolBalance - 1, enumeratorPoolBalance ) == enumeratorPoolBalance ) 359 | return true; 360 | 361 | spin.SpinOnce(); 362 | } 363 | } 364 | 365 | private void PushToPool() 366 | { 367 | SpinWait spin = new SpinWait(); 368 | while ( true ) 369 | { 370 | Segment oldHead = Volatile.Read( ref _queue._segmentPoolHead ); 371 | Volatile.Write( ref _nextPooledSegment, oldHead ); 372 | 373 | if ( Interlocked.CompareExchange( ref _queue._segmentPoolHead, this, oldHead ) == oldHead ) 374 | break; 375 | 376 | spin.SpinOnce(); 377 | } 378 | } 379 | 380 | private Segment TryPopSegmentFromPool() 381 | { 382 | SpinWait spin = new SpinWait(); 383 | while ( true ) 384 | { 385 | Segment oldHead = Volatile.Read( ref _queue._segmentPoolHead ); 386 | if ( oldHead == null ) 387 | return null; 388 | 389 | if ( Interlocked.CompareExchange( ref _queue._segmentPoolHead, oldHead._nextPooledSegment, oldHead ) == oldHead ) 390 | { 391 | Volatile.Write( ref oldHead._nextPooledSegment, null ); 392 | return oldHead; 393 | } 394 | 395 | spin.SpinOnce(); 396 | } 397 | } 398 | 399 | /// 400 | /// It's possible for the appenders to read the tail reference before it's updated and to try appending to the segment while it's being pooled. 401 | /// There's no cheap way to prevent it, so we have to be prepared that an append can succeed as fast as we reset or . 402 | /// This means this method must *NOT* be called on putting the segment into the pool, because it could lead to items/awaiters being stored somewhere in the pool. 403 | /// Since there's no guarantee that the segment will ever be reused, this effectively means losing data (or, in the best-case scenario, completely screwing up the item order). 404 | /// We prevent this disaster by calling this method on taking segment from the pool, instead of putting it there. 405 | /// This way even if such append happens, we're about the reattach the segment to the queue and the data won't be lost. 406 | /// 407 | private Segment ResetAfterTakingFromPool() 408 | { 409 | /// We must reset before and . 410 | /// Otherwise appenders could successfully increment a pointer and mess with the slots before they are ready to be messed with. 411 | for ( int i = 0; i < SegmentSize; i++ ) 412 | { 413 | /// We can't simply overwrite the state, because it's possible that the slot loser has not finished yet. 414 | SpinWait spin = new SpinWait(); 415 | while ( Interlocked.CompareExchange( ref _slotStates[ i ], SlotState.None, SlotState.Cleared ) != SlotState.Cleared ) 416 | spin.SpinOnce(); 417 | } 418 | 419 | Volatile.Write( ref _awaiterIndex, -1 ); 420 | Volatile.Write( ref _itemIndex, -1 ); 421 | 422 | return this; 423 | } 424 | 425 | private static bool IsLastSlot( int slot ) => slot == SegmentSize - 1; 426 | 427 | private Segment SpinUntilNextSegmentIsReady() 428 | { 429 | SpinWait spin = new SpinWait(); 430 | while ( VolatileNext == null ) 431 | spin.SpinOnce(); 432 | 433 | return VolatileNext; 434 | } 435 | 436 | private Segment GrowSegment() 437 | { 438 | Segment newTail = TryPopSegmentFromPool()?.ResetAfterTakingFromPool() ?? new Segment( _queue ); 439 | newTail.SegmentID = _segmentID + 1; 440 | Volatile.Write( ref _next, newTail ); 441 | return newTail; 442 | } 443 | 444 | private int SpinUntilStateIsResolvedAndReturnState( int slot ) 445 | { 446 | SpinWait spin = new SpinWait(); 447 | int slotState; 448 | while ( true ) 449 | { 450 | slotState = Volatile.Read( ref _slotStates[ slot ] ); 451 | if ( slotState != SlotState.None ) 452 | break; 453 | 454 | spin.SpinOnce(); 455 | } 456 | 457 | return slotState; 458 | } 459 | 460 | public Enumerator GetEnumerator() => new Enumerator( this ); 461 | IEnumerator IEnumerable.GetEnumerator() => new BoxedEnumerator( GetEnumerator() ); 462 | IEnumerator IEnumerable.GetEnumerator() => ( this as IEnumerable ).GetEnumerator(); 463 | 464 | private static class SlotState 465 | { 466 | public const int None = 0; 467 | public const int HasItem = 1; 468 | public const int HasAwaiter = 2; 469 | public const int Cleared = 3; 470 | } 471 | 472 | public struct Enumerator : IEnumerator 473 | { 474 | private readonly Segment _segment; 475 | private int _currentSlot; 476 | private int _effectiveLength; 477 | 478 | public Enumerator( Segment segment ) 479 | { 480 | _segment = segment; 481 | _currentSlot = Int32.MinValue; 482 | _effectiveLength = Int32.MinValue; 483 | Current = default( T ); 484 | } 485 | 486 | public T Current { get; private set; } 487 | object IEnumerator.Current => Current; 488 | 489 | public bool MoveNext() 490 | { 491 | if ( _currentSlot == Int32.MinValue ) 492 | { 493 | /// Items in slots 0 .. are taken by awaiters, so they are no longer considered stored in the queue. 494 | _currentSlot = _segment.SlotReferenceToCount( ref _segment._awaiterIndex ); 495 | 496 | /// is the last slot an item actually exists at at the moment, so we shouldn't enumerate through the default values that are stored further. 497 | _effectiveLength = _segment.SlotReferenceToCount( ref _segment._itemIndex ); 498 | } 499 | 500 | while ( _currentSlot < _effectiveLength ) 501 | { 502 | int slotState = _segment.SpinUntilStateIsResolvedAndReturnState( _currentSlot ); 503 | Current = _segment._items[ _currentSlot ]; 504 | _currentSlot++; 505 | 506 | if ( slotState == SlotState.HasItem ) 507 | return true; 508 | } 509 | 510 | return false; 511 | } 512 | 513 | public void Dispose() 514 | { 515 | } 516 | 517 | public void Reset() 518 | { 519 | } 520 | } 521 | } 522 | } 523 | } 524 | -------------------------------------------------------------------------------- /AsyncCollections/AsyncStack.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Concurrent; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace HellBrick.Collections 9 | { 10 | /// 11 | /// Represents a thread-safe stack that allows asynchronous consuming. 12 | /// 13 | /// The type of the items contained in the stack. 14 | public class AsyncStack : AsyncCollection 15 | { 16 | /// 17 | /// Initializes a new empty instance of . 18 | /// 19 | public AsyncStack() : base( new ConcurrentStack() ) { } 20 | 21 | /// 22 | /// Initializes a new instance of that contains elements copied from a specified collection. 23 | /// 24 | /// The collection whose elements are copied to the new stack. 25 | public AsyncStack( IEnumerable collection ) : base( new ConcurrentStack( collection ) ) { } 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /AsyncCollections/IAsyncBatchCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace HellBrick.Collections 7 | { 8 | public interface IAsyncBatchCollection : IReadOnlyCollection> 9 | { 10 | int BatchSize { get; } 11 | 12 | void Add( T item ); 13 | void Flush(); 14 | ValueTask> TakeAsync( CancellationToken cancellationToken ); 15 | } 16 | 17 | public static class AsyncBatchCollectionExtensions 18 | { 19 | public static ValueTask> TakeAsync( this IAsyncBatchCollection collection ) => collection.TakeAsync( CancellationToken.None ); 20 | public static TimerAsyncBatchQueue WithFlushEvery( this IAsyncBatchCollection collection, TimeSpan flushPeriod ) => new TimerAsyncBatchQueue( collection, flushPeriod ); 21 | } 22 | } -------------------------------------------------------------------------------- /AsyncCollections/IAsyncCollection.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Threading; 4 | using System.Threading.Tasks; 5 | 6 | namespace HellBrick.Collections 7 | { 8 | /// 9 | /// Represents a thread-safe collection that allows asynchronous consuming. 10 | /// 11 | /// The type of the items contained in the collection. 12 | public interface IAsyncCollection : IReadOnlyCollection 13 | { 14 | /// 15 | /// Gets an amount of pending item requests. 16 | /// 17 | int AwaiterCount { get; } 18 | 19 | /// 20 | /// Adds an item to the collection. 21 | /// 22 | /// The item to add to the collection. 23 | void Add( T item ); 24 | 25 | /// 26 | /// Removes and returns an item from the collection in an asynchronous manner. 27 | /// 28 | ValueTask TakeAsync( CancellationToken cancellationToken ); 29 | } 30 | 31 | public static class AsyncCollectionExtensions 32 | { 33 | /// 34 | /// Removes and returns an item from the collection in an asynchronous manner. 35 | /// 36 | public static ValueTask TakeAsync( this IAsyncCollection collection ) 37 | { 38 | return collection.TakeAsync( CancellationToken.None ); 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /AsyncCollections/Internal/BitArray32.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HellBrick.Collections.Internal 8 | { 9 | internal struct BitArray32 : IEquatable 10 | { 11 | public const int BitCapacity = sizeof( uint ) * 8; 12 | public static readonly BitArray32 Empty = new BitArray32(); 13 | 14 | private readonly uint _value; 15 | 16 | public BitArray32( uint value ) 17 | { 18 | _value = value; 19 | } 20 | 21 | public BitArray32 WithBitSet( int index ) => new BitArray32( _value | GetMask( index ) ); 22 | 23 | public bool IsBitSet( int index ) 24 | { 25 | uint mask = GetMask( index ); 26 | return ( _value & mask ) == mask; 27 | } 28 | 29 | public override string ToString() 30 | { 31 | char[] chars = new char[ BitCapacity ]; 32 | 33 | for ( int index = 0; index < BitCapacity; index++ ) 34 | { 35 | char bitChar = IsBitSet( index ) ? '1' : '0'; 36 | chars[ index ] = bitChar; 37 | } 38 | 39 | return new string( chars ); 40 | } 41 | 42 | private static uint GetMask( int index ) => ( 1u << index ); 43 | 44 | #region IEquatable 45 | 46 | public override int GetHashCode() => EqualityComparer.Default.GetHashCode( _value ); 47 | public bool Equals( BitArray32 other ) => EqualityComparer.Default.Equals( _value, other._value ); 48 | public override bool Equals( object obj ) => obj is BitArray32 && Equals( (BitArray32) obj ); 49 | 50 | public static bool operator ==( BitArray32 x, BitArray32 y ) => x.Equals( y ); 51 | public static bool operator !=( BitArray32 x, BitArray32 y ) => !x.Equals( y ); 52 | 53 | #endregion 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /AsyncCollections/Internal/BoxedEnumerator.cs: -------------------------------------------------------------------------------- 1 | using System.Collections; 2 | using System.Collections.Generic; 3 | 4 | namespace HellBrick.Collections.Internal 5 | { 6 | /// Turns out iterating through manually boxed iterator is a bit faster than through automatically boxed one. 7 | internal class BoxedEnumerator : IEnumerator where TEnumerator : struct, IEnumerator 8 | { 9 | private TEnumerator _structEnumerator; 10 | 11 | public BoxedEnumerator( TEnumerator structEnumerator ) 12 | { 13 | _structEnumerator = structEnumerator; 14 | } 15 | 16 | public TItem Current => _structEnumerator.Current; 17 | object IEnumerator.Current => Current; 18 | public void Dispose() => _structEnumerator.Dispose(); 19 | public bool MoveNext() => _structEnumerator.MoveNext(); 20 | public void Reset() => _structEnumerator.Reset(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /AsyncCollections/Internal/CancelledTask.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HellBrick.Collections.Internal 8 | { 9 | internal static class CanceledValueTask 10 | { 11 | public static readonly ValueTask Value = CreateCanceledTask(); 12 | 13 | private static ValueTask CreateCanceledTask() 14 | { 15 | TaskCompletionSource tcs = new TaskCompletionSource(); 16 | tcs.SetCanceled(); 17 | return new ValueTask( tcs.Task ); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AsyncCollections/Internal/CompletionSourceAwaiterFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | using System.Threading.Tasks; 7 | 8 | namespace HellBrick.Collections.Internal 9 | { 10 | internal struct CompletionSourceAwaiterFactory : IAwaiterFactory, IEquatable> 11 | { 12 | private readonly CancellationToken _cancellationToken; 13 | 14 | public CompletionSourceAwaiterFactory( CancellationToken cancellationToken ) 15 | { 16 | _cancellationToken = cancellationToken; 17 | } 18 | 19 | public IAwaiter CreateAwaiter() => new CompletionSourceAwaiter( _cancellationToken ); 20 | 21 | /// 22 | /// A simple wrapper that implements . 23 | /// 24 | private class CompletionSourceAwaiter : IAwaiter 25 | { 26 | private readonly TaskCompletionSource _completionSource; 27 | private readonly CancellationTokenRegistration _registration; 28 | 29 | public CompletionSourceAwaiter( CancellationToken cancellationToken ) 30 | { 31 | _completionSource = new TaskCompletionSource(); 32 | Task = new ValueTask( _completionSource.Task.WithYield() ); 33 | 34 | _registration = cancellationToken.Register( 35 | state => 36 | { 37 | TaskCompletionSource awaiter = state as TaskCompletionSource; 38 | awaiter.TrySetCanceled(); 39 | }, 40 | _completionSource, 41 | useSynchronizationContext: false ); 42 | } 43 | 44 | public bool TrySetResult( T result ) 45 | { 46 | _registration.Dispose(); 47 | return _completionSource.TrySetResult( result ); 48 | } 49 | 50 | public ValueTask Task { get; } 51 | } 52 | 53 | #region IEquatable> 54 | 55 | public override int GetHashCode() => EqualityComparer.Default.GetHashCode( _cancellationToken ); 56 | public bool Equals( CompletionSourceAwaiterFactory other ) => _cancellationToken == other._cancellationToken; 57 | public override bool Equals( object obj ) => obj is CompletionSourceAwaiterFactory && Equals( (CompletionSourceAwaiterFactory) obj ); 58 | 59 | public static bool operator ==( CompletionSourceAwaiterFactory x, CompletionSourceAwaiterFactory y ) => x.Equals( y ); 60 | public static bool operator !=( CompletionSourceAwaiterFactory x, CompletionSourceAwaiterFactory y ) => !x.Equals( y ); 61 | 62 | #endregion 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /AsyncCollections/Internal/ExclusiveCompletionSourceGroup.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Collections.Specialized; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace HellBrick.Collections.Internal 10 | { 11 | /// 12 | /// A set of exclusive awaiters that allows only one of the awaiters to be completed. 13 | /// 14 | internal class ExclusiveCompletionSourceGroup 15 | { 16 | private int _completedSource = State.Locked; 17 | private readonly TaskCompletionSource> _realCompetionSource = new TaskCompletionSource>(); 18 | private BitArray32 _awaitersCreated = BitArray32.Empty; 19 | private CancellationRegistrationHolder _cancellationRegistrationHolder; 20 | 21 | public ExclusiveCompletionSourceGroup() 22 | { 23 | Task = _realCompetionSource.Task.WithYield(); 24 | } 25 | 26 | public Task> Task { get; } 27 | 28 | public bool IsAwaiterCreated( int index ) => _awaitersCreated.IsBitSet( index ); 29 | public Factory CreateAwaiterFactory( int index ) => new Factory( this, index ); 30 | 31 | private IAwaiter CreateAwaiter( int index ) 32 | { 33 | _awaitersCreated = _awaitersCreated.WithBitSet( index ); 34 | return new ExclusiveCompletionSource( this, index ); 35 | } 36 | 37 | public void MarkAsResolved() => Interlocked.CompareExchange( ref _completedSource, State.Canceled, State.Unlocked ); 38 | 39 | public void UnlockCompetition( CancellationToken cancellationToken ) 40 | { 41 | CancellationTokenRegistration registration = cancellationToken 42 | .Register 43 | ( 44 | state => 45 | { 46 | ExclusiveCompletionSourceGroup group = state as ExclusiveCompletionSourceGroup; 47 | 48 | /// There are 2 cases here. 49 | /// 50 | /// #1: The token is canceled before is called, but after the token is validated higher up the stack. 51 | /// Is this is the case, the cancellation callbak will be called synchronously while is still set to . 52 | /// So the competition will never progress to and we have to check for this explicitly. 53 | /// 54 | /// #2: We're canceled after the competition has been unlocked. 55 | /// If this is the case, we have a simple race against the awaiters to progress from to . 56 | if ( group.TryTransitionToCanceledIfStateIs( State.Locked ) || group.TryTransitionToCanceledIfStateIs( State.Unlocked ) ) 57 | group._realCompetionSource.SetCanceled(); 58 | }, 59 | this, 60 | useSynchronizationContext: false 61 | ); 62 | 63 | // We can't do volatile reads/writes on a custom value type field, so we have to wrap the registration into a holder instance. 64 | // But there's no point in allocating the wrapper if the token can never be canceled. 65 | if ( cancellationToken.CanBeCanceled ) 66 | Volatile.Write( ref _cancellationRegistrationHolder, new CancellationRegistrationHolder( registration ) ); 67 | 68 | // If the cancellation was processed synchronously, the state will already be set to Canceled and we must *NOT* unlock the competition. 69 | Interlocked.CompareExchange( ref _completedSource, State.Unlocked, State.Locked ); 70 | } 71 | 72 | private bool TryTransitionToCanceledIfStateIs( int requiredState ) => Interlocked.CompareExchange( ref _completedSource, State.Canceled, requiredState ) == requiredState; 73 | 74 | private static class State 75 | { 76 | public const int Locked = -1; 77 | public const int Unlocked = -2; 78 | public const int Canceled = Int32.MinValue; 79 | } 80 | 81 | private class CancellationRegistrationHolder 82 | { 83 | public CancellationRegistrationHolder( CancellationTokenRegistration registration ) 84 | { 85 | Registration = registration; 86 | } 87 | 88 | public CancellationTokenRegistration Registration { get; } 89 | } 90 | 91 | private class ExclusiveCompletionSource : IAwaiter 92 | { 93 | private static readonly ValueTask _neverEndingTask = new ValueTask( new TaskCompletionSource().Task ); 94 | private readonly ExclusiveCompletionSourceGroup _group; 95 | private readonly int _id; 96 | 97 | public ExclusiveCompletionSource( ExclusiveCompletionSourceGroup group, int id ) 98 | { 99 | _group = group; 100 | _id = id; 101 | } 102 | 103 | public bool TrySetResult( T result ) 104 | { 105 | SpinWait spin = new SpinWait(); 106 | 107 | while ( true ) 108 | { 109 | int completedSource = Interlocked.CompareExchange( ref _group._completedSource, _id, State.Unlocked ); 110 | 111 | if ( completedSource == State.Unlocked ) 112 | { 113 | // We are the champions! 114 | _group._realCompetionSource.SetResult( new AnyResult( result, _id ) ); 115 | 116 | // This also means we're the ones responsible for disposing the cancellation registration. 117 | // It's important to remember the holder can be null if the token is non-cancellable. 118 | Volatile.Read( ref _group._cancellationRegistrationHolder )?.Registration.Dispose(); 119 | return true; 120 | } 121 | 122 | if ( completedSource == State.Locked ) 123 | { 124 | // The competition has not started yet. 125 | spin.SpinOnce(); 126 | continue; 127 | } 128 | 129 | // Everything else means we've lost the competition and another completion source has got the result 130 | return false; 131 | } 132 | } 133 | 134 | // The value will never be actually used. 135 | public ValueTask Task => _neverEndingTask; 136 | } 137 | 138 | public struct Factory : IAwaiterFactory, IEquatable 139 | { 140 | private readonly ExclusiveCompletionSourceGroup _group; 141 | private readonly int _index; 142 | 143 | public Factory( ExclusiveCompletionSourceGroup group, int index ) 144 | { 145 | _group = group; 146 | _index = index; 147 | } 148 | 149 | public IAwaiter CreateAwaiter() => _group.CreateAwaiter( _index ); 150 | 151 | #region IEquatable 152 | 153 | public override int GetHashCode() 154 | { 155 | unchecked 156 | { 157 | const int prime = -1521134295; 158 | int hash = 12345701; 159 | hash = hash * prime + EqualityComparer>.Default.GetHashCode( _group ); 160 | hash = hash * prime + EqualityComparer.Default.GetHashCode( _index ); 161 | return hash; 162 | } 163 | } 164 | 165 | public bool Equals( Factory other ) => EqualityComparer>.Default.Equals( _group, other._group ) && EqualityComparer.Default.Equals( _index, other._index ); 166 | public override bool Equals( object obj ) => obj is Factory && Equals( (Factory) obj ); 167 | 168 | public static bool operator ==( Factory x, Factory y ) => x.Equals( y ); 169 | public static bool operator !=( Factory x, Factory y ) => !x.Equals( y ); 170 | 171 | #endregion 172 | } 173 | } 174 | } -------------------------------------------------------------------------------- /AsyncCollections/Internal/IAwaiter.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HellBrick.Collections.Internal 8 | { 9 | /// 10 | /// Represents an abstract item awaiter. 11 | /// 12 | internal interface IAwaiter 13 | { 14 | /// 15 | /// Attempts to complete the awaiter with a specified result. 16 | /// Returns false if the awaiter has been canceled. 17 | /// 18 | bool TrySetResult( T result ); 19 | 20 | /// 21 | /// The task that's completed when the awaiter gets the result. 22 | /// 23 | ValueTask Task { get; } 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /AsyncCollections/Internal/IAwaiterFactory.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HellBrick.Collections.Internal 8 | { 9 | internal interface IAwaiterFactory 10 | { 11 | IAwaiter CreateAwaiter(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /AsyncCollections/Internal/SelectManyStructEnumererator.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading.Tasks; 7 | 8 | namespace HellBrick.Collections.Internal 9 | { 10 | internal struct SelectManyStructEnumererator : IEnumerator 11 | where TSourceEnumerator : struct, IEnumerator 12 | where TTargetEnumerator : struct, IEnumerator 13 | { 14 | private readonly Func _selector; 15 | private TSourceEnumerator _sourceEnumerator; 16 | 17 | private TTargetEnumerator _currentTargetEnumerator; 18 | private bool _hasCurrentTarget; 19 | 20 | public SelectManyStructEnumererator( TSourceEnumerator sourceEnumerator, Func selector ) 21 | { 22 | _sourceEnumerator = sourceEnumerator; 23 | _selector = selector; 24 | 25 | _hasCurrentTarget = false; 26 | _currentTargetEnumerator = default( TTargetEnumerator ); 27 | Current = default( TTarget ); 28 | } 29 | 30 | public TTarget Current { get; private set; } 31 | object IEnumerator.Current => Current; 32 | 33 | public bool MoveNext() 34 | { 35 | do 36 | { 37 | if ( !_hasCurrentTarget && !TryMoveToNextTarget() ) 38 | return false; 39 | 40 | if ( _currentTargetEnumerator.MoveNext() ) 41 | { 42 | Current = _currentTargetEnumerator.Current; 43 | return true; 44 | } 45 | } 46 | while ( TryMoveToNextTarget() ); 47 | 48 | return false; 49 | } 50 | 51 | private bool TryMoveToNextTarget() 52 | { 53 | TryDisposeCurrentTarget(); 54 | 55 | _hasCurrentTarget = _sourceEnumerator.MoveNext(); 56 | if ( _hasCurrentTarget ) 57 | _currentTargetEnumerator = _selector( _sourceEnumerator.Current ); 58 | 59 | return _hasCurrentTarget; 60 | } 61 | 62 | private void TryDisposeCurrentTarget() 63 | { 64 | if ( _hasCurrentTarget ) 65 | _currentTargetEnumerator.Dispose(); 66 | } 67 | 68 | public void Dispose() => TryDisposeCurrentTarget(); 69 | 70 | public void Reset() 71 | { 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /AsyncCollections/Internal/TaskExtensions.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Linq.Expressions; 5 | using System.Reflection; 6 | using System.Text; 7 | using System.Threading.Tasks; 8 | 9 | namespace HellBrick.Collections.Internal 10 | { 11 | internal static class TaskExtensions 12 | { 13 | public static async Task WithYield( this Task task ) 14 | { 15 | var result = await task.ConfigureAwait( false ); 16 | await Task.Yield(); 17 | return result; 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /AsyncCollections/PrioritizedItem.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading.Tasks; 6 | 7 | namespace HellBrick.Collections 8 | { 9 | public struct PrioritizedItem : IEquatable> 10 | { 11 | public PrioritizedItem( T item, int priority ) 12 | { 13 | Item = item; 14 | Priority = priority; 15 | } 16 | 17 | public T Item { get; } 18 | public int Priority { get; } 19 | 20 | public override string ToString() => $"{nameof( Item )}: {Item}, {nameof( Priority )}: {Priority}"; 21 | 22 | public override int GetHashCode() 23 | { 24 | unchecked 25 | { 26 | const int prime = -1521134295; 27 | int hash = 12345701; 28 | hash = hash * prime + EqualityComparer.Default.GetHashCode( Item ); 29 | hash = hash * prime + EqualityComparer.Default.GetHashCode( Priority ); 30 | return hash; 31 | } 32 | } 33 | 34 | public bool Equals( PrioritizedItem other ) => EqualityComparer.Default.Equals( Item, other.Item ) && EqualityComparer.Default.Equals( Priority, other.Priority ); 35 | public override bool Equals( object obj ) => obj is PrioritizedItem && Equals( (PrioritizedItem) obj ); 36 | 37 | public static bool operator ==( PrioritizedItem x, PrioritizedItem y ) => x.Equals( y ); 38 | public static bool operator !=( PrioritizedItem x, PrioritizedItem y ) => !x.Equals( y ); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /AsyncCollections/Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle( "AsyncCollections" )] 9 | [assembly: AssemblyDescription( "This package contains a set of lock-free thread-safe collections designed to be used asynchronously." )] 10 | [assembly: AssemblyConfiguration( "" )] 11 | [assembly: AssemblyCompany( "HellBrick" )] 12 | [assembly: AssemblyProduct( "AsyncCollections" )] 13 | [assembly: AssemblyCopyright( "Copyright © 2014" )] 14 | [assembly: AssemblyTrademark( "" )] 15 | [assembly: AssemblyCulture( "" )] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible( false )] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid( "765e4d62-82bd-43e9-b369-ed16785978d9" )] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion( "1.3.8.0" )] 36 | [assembly: AssemblyFileVersion( "1.3.8.0" )] 37 | 38 | [assembly: InternalsVisibleTo( "AsyncCollections.Test" )] -------------------------------------------------------------------------------- /AsyncCollections/TimerAsyncBatchQueue.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections; 3 | using System.Collections.Generic; 4 | using System.Linq; 5 | using System.Text; 6 | using System.Threading; 7 | using System.Threading.Tasks; 8 | 9 | namespace HellBrick.Collections 10 | { 11 | public class TimerAsyncBatchQueue : IAsyncBatchCollection, IDisposable 12 | { 13 | private readonly Timer _flushTimer; 14 | private readonly IAsyncBatchCollection _innerCollection; 15 | 16 | public TimerAsyncBatchQueue( IAsyncBatchCollection innerCollection, TimeSpan flushPeriod ) 17 | { 18 | _innerCollection = innerCollection; 19 | _flushTimer = new Timer( _ => Flush(), null, flushPeriod, flushPeriod ); 20 | } 21 | 22 | public int BatchSize => _innerCollection.BatchSize; 23 | public int Count => _innerCollection.Count; 24 | public void Add( T item ) => _innerCollection.Add( item ); 25 | public ValueTask> TakeAsync( CancellationToken cancellationToken ) => _innerCollection.TakeAsync( cancellationToken ); 26 | public void Flush() => _innerCollection.Flush(); 27 | public IEnumerator> GetEnumerator() => _innerCollection.GetEnumerator(); 28 | IEnumerator IEnumerable.GetEnumerator() => ( _innerCollection as IEnumerable ).GetEnumerator(); 29 | 30 | public void Dispose() => _flushTimer.Dispose(); 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /AsyncCollections/ValueTask/ConfiguredValueTaskAwaitable.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Runtime.InteropServices; 6 | using System.Threading.Tasks; 7 | 8 | #pragma warning disable OverrideGetHashCode // Structs should override GetHashCode() 9 | #pragma warning disable ImplementOperatorEqualsEqualsToken // Structs should implement operator == 10 | #pragma warning disable ImplementOperatorExclamationEqualsToken // Structs should implement operator != 11 | #pragma warning disable HBStructImplementIEquatable // Structs should implement IEquatable 12 | #pragma warning disable OverrideEquals // Structs should override Equals() 13 | 14 | namespace System.Runtime.CompilerServices 15 | { 16 | /// Provides an awaitable type that enables configured awaits on a . 17 | /// The type of the result produced. 18 | [StructLayout( LayoutKind.Auto )] 19 | public struct ConfiguredValueTaskAwaitable 20 | { 21 | /// The wrapped . 22 | private readonly ValueTask _value; 23 | /// true to attempt to marshal the continuation back to the original context captured; otherwise, false. 24 | private readonly bool _continueOnCapturedContext; 25 | 26 | /// Initializes the awaitable. 27 | /// The wrapped . 28 | /// 29 | /// true to attempt to marshal the continuation back to the original synchronization context captured; otherwise, false. 30 | /// 31 | internal ConfiguredValueTaskAwaitable( ValueTask value, bool continueOnCapturedContext ) 32 | { 33 | _value = value; 34 | _continueOnCapturedContext = continueOnCapturedContext; 35 | } 36 | 37 | /// Returns an awaiter for this instance. 38 | public ConfiguredValueTaskAwaiter GetAwaiter() 39 | { 40 | return new ConfiguredValueTaskAwaiter( _value, _continueOnCapturedContext ); 41 | } 42 | 43 | /// Provides an awaiter for a . 44 | [StructLayout( LayoutKind.Auto )] 45 | public struct ConfiguredValueTaskAwaiter : ICriticalNotifyCompletion 46 | { 47 | /// The value being awaited. 48 | private readonly ValueTask _value; 49 | /// The value to pass to ConfigureAwait. 50 | private readonly bool _continueOnCapturedContext; 51 | 52 | /// Initializes the awaiter. 53 | /// The value to be awaited. 54 | /// The value to pass to ConfigureAwait. 55 | internal ConfiguredValueTaskAwaiter( ValueTask value, bool continueOnCapturedContext ) 56 | { 57 | _value = value; 58 | _continueOnCapturedContext = continueOnCapturedContext; 59 | } 60 | 61 | /// Gets whether the has completed. 62 | public bool IsCompleted { get { return _value.IsCompleted; } } 63 | 64 | /// Gets the result of the ValueTask. 65 | public TResult GetResult() 66 | { 67 | return _value._task == null ? 68 | _value._result : 69 | _value._task.GetAwaiter().GetResult(); 70 | } 71 | 72 | /// Schedules the continuation action for the . 73 | public void OnCompleted( Action continuation ) 74 | { 75 | _value.AsTask().ConfigureAwait( _continueOnCapturedContext ).GetAwaiter().OnCompleted( continuation ); 76 | } 77 | 78 | /// Schedules the continuation action for the . 79 | public void UnsafeOnCompleted( Action continuation ) 80 | { 81 | _value.AsTask().ConfigureAwait( _continueOnCapturedContext ).GetAwaiter().UnsafeOnCompleted( continuation ); 82 | } 83 | } 84 | } 85 | } 86 | 87 | #pragma warning restore OverrideEquals // Structs should override Equals() 88 | #pragma warning restore HBStructImplementIEquatable // Structs should implement IEquatable 89 | #pragma warning restore ImplementOperatorExclamationEqualsToken // Structs should implement operator != 90 | #pragma warning restore ImplementOperatorEqualsEqualsToken // Structs should implement operator == 91 | #pragma warning restore OverrideGetHashCode // Structs should override GetHashCode() -------------------------------------------------------------------------------- /AsyncCollections/ValueTask/ValueTask.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Collections.Generic; 6 | using System.Runtime.CompilerServices; 7 | using System.Runtime.InteropServices; 8 | 9 | namespace System.Threading.Tasks 10 | { 11 | /// 12 | /// Provides a value type that wraps a and a , 13 | /// only one of which is used. 14 | /// 15 | /// The type of the result. 16 | /// 17 | /// 18 | /// Methods may return an instance of this value type when it's likely that the result of their 19 | /// operations will be available synchronously and when the method is expected to be invoked so 20 | /// frequently that the cost of allocating a new for each call will 21 | /// be prohibitive. 22 | /// 23 | /// 24 | /// There are tradeoffs to using a instead of a . 25 | /// For example, while a can help avoid an allocation in the case where the 26 | /// successful result is available synchronously, it also contains two fields whereas a 27 | /// as a reference type is a single field. This means that a method call ends up returning two fields worth of 28 | /// data instead of one, which is more data to copy. It also means that if a method that returns one of these 29 | /// is awaited within an async method, the state machine for that async method will be larger due to needing 30 | /// to store the struct that's two fields instead of a single reference. 31 | /// 32 | /// 33 | /// Further, for uses other than consuming the result of an asynchronous operation via await, 34 | /// can lead to a more convoluted programming model, which can in turn actually 35 | /// lead to more allocations. For example, consider a method that could return either a 36 | /// with a cached task as a common result or a . If the consumer of the result 37 | /// wants to use it as a , such as to use with in methods like Task.WhenAll and Task.WhenAny, 38 | /// the would first need to be converted into a using 39 | /// , which leads to an allocation that would have been avoided if a cached 40 | /// had been used in the first place. 41 | /// 42 | /// 43 | /// As such, the default choice for any asynchronous method should be to return a or 44 | /// . Only if performance analysis proves it worthwhile should a 45 | /// be used instead of . There is no non-generic version of 46 | /// as the Task.CompletedTask property may be used to hand back a successfully completed singleton in the case where 47 | /// a -returning method completes synchronously and successfully. 48 | /// 49 | /// 50 | [StructLayout( LayoutKind.Auto )] 51 | public struct ValueTask : IEquatable> 52 | { 53 | /// The task to be used if the operation completed asynchronously or if it completed synchronously but non-successfully. 54 | internal readonly Task _task; 55 | /// The result to be used if the operation completed successfully synchronously. 56 | internal readonly TResult _result; 57 | 58 | /// Initialize the with the result of the successful operation. 59 | /// The result. 60 | public ValueTask( TResult result ) 61 | { 62 | _task = null; 63 | _result = result; 64 | } 65 | 66 | /// 67 | /// Initialize the with a that represents the operation. 68 | /// 69 | /// The task. 70 | public ValueTask( Task task ) 71 | { 72 | if ( task == null ) 73 | { 74 | throw new ArgumentNullException( nameof( task ) ); 75 | } 76 | 77 | _task = task; 78 | _result = default( TResult ); 79 | } 80 | 81 | /// Returns the hash code for this instance. 82 | public override int GetHashCode() 83 | { 84 | return 85 | _task != null ? _task.GetHashCode() : 86 | _result != null ? _result.GetHashCode() : 87 | 0; 88 | } 89 | 90 | /// Returns a value indicating whether this value is equal to a specified . 91 | public override bool Equals( object obj ) 92 | { 93 | return 94 | obj is ValueTask && 95 | Equals( (ValueTask) obj ); 96 | } 97 | 98 | /// Returns a value indicating whether this value is equal to a specified value. 99 | public bool Equals( ValueTask other ) 100 | { 101 | return _task != null || other._task != null ? 102 | _task == other._task : 103 | EqualityComparer.Default.Equals( _result, other._result ); 104 | } 105 | 106 | /// Returns a value indicating whether two values are equal. 107 | public static bool operator ==( ValueTask left, ValueTask right ) 108 | { 109 | return left.Equals( right ); 110 | } 111 | 112 | /// Returns a value indicating whether two values are not equal. 113 | public static bool operator !=( ValueTask left, ValueTask right ) 114 | { 115 | return !left.Equals( right ); 116 | } 117 | 118 | /// 119 | /// Gets a object to represent this ValueTask. It will 120 | /// either return the wrapped task object if one exists, or it'll manufacture a new 121 | /// task object to represent the result. 122 | /// 123 | public Task AsTask() 124 | { 125 | // Return the task if we were constructed from one, otherwise manufacture one. We don't 126 | // cache the generated task into _task as it would end up changing both equality comparison 127 | // and the hash code we generate in GetHashCode. 128 | return _task ?? Task.FromResult( _result ); 129 | } 130 | 131 | /// Gets whether the represents a completed operation. 132 | public bool IsCompleted { get { return _task == null || _task.IsCompleted; } } 133 | 134 | /// Gets whether the represents a successfully completed operation. 135 | public bool IsCompletedSuccessfully { get { return _task == null || _task.Status == TaskStatus.RanToCompletion; } } 136 | 137 | /// Gets whether the represents a failed operation. 138 | public bool IsFaulted { get { return _task != null && _task.IsFaulted; } } 139 | 140 | /// Gets whether the represents a canceled operation. 141 | public bool IsCanceled { get { return _task != null && _task.IsCanceled; } } 142 | 143 | /// Gets the result. 144 | public TResult Result { get { return _task == null ? _result : _task.GetAwaiter().GetResult(); } } 145 | 146 | /// Gets an awaiter for this value. 147 | public ValueTaskAwaiter GetAwaiter() 148 | { 149 | return new ValueTaskAwaiter( this ); 150 | } 151 | 152 | /// Configures an awaiter for this value. 153 | /// 154 | /// true to attempt to marshal the continuation back to the captured context; otherwise, false. 155 | /// 156 | public ConfiguredValueTaskAwaitable ConfigureAwait( bool continueOnCapturedContext ) 157 | { 158 | return new ConfiguredValueTaskAwaitable( this, continueOnCapturedContext: continueOnCapturedContext ); 159 | } 160 | 161 | /// Gets a string-representation of this . 162 | public override string ToString() 163 | { 164 | if ( _task != null ) 165 | { 166 | return _task.Status == TaskStatus.RanToCompletion && _task.Result != null ? 167 | _task.Result.ToString() : 168 | string.Empty; 169 | } 170 | else 171 | { 172 | return _result != null ? 173 | _result.ToString() : 174 | string.Empty; 175 | } 176 | } 177 | } 178 | } -------------------------------------------------------------------------------- /AsyncCollections/ValueTask/ValueTaskAwaiter.cs: -------------------------------------------------------------------------------- 1 | // Licensed to the .NET Foundation under one or more agreements. 2 | // The .NET Foundation licenses this file to you under the MIT license. 3 | // See the LICENSE file in the project root for more information. 4 | 5 | using System.Threading.Tasks; 6 | 7 | #pragma warning disable OverrideGetHashCode // Structs should override GetHashCode() 8 | #pragma warning disable ImplementOperatorEqualsEqualsToken // Structs should implement operator == 9 | #pragma warning disable ImplementOperatorExclamationEqualsToken // Structs should implement operator != 10 | #pragma warning disable HBStructImplementIEquatable // Structs should implement IEquatable 11 | #pragma warning disable OverrideEquals // Structs should override Equals() 12 | 13 | namespace System.Runtime.CompilerServices 14 | { 15 | /// Provides an awaiter for a . 16 | public struct ValueTaskAwaiter : ICriticalNotifyCompletion 17 | { 18 | /// The value being awaited. 19 | private readonly ValueTask _value; 20 | 21 | /// Initializes the awaiter. 22 | /// The value to be awaited. 23 | internal ValueTaskAwaiter( ValueTask value ) { _value = value; } 24 | 25 | /// Gets whether the has completed. 26 | public bool IsCompleted { get { return _value.IsCompleted; } } 27 | 28 | /// Gets the result of the ValueTask. 29 | public TResult GetResult() 30 | { 31 | return _value._task == null ? 32 | _value._result : 33 | _value._task.GetAwaiter().GetResult(); 34 | } 35 | 36 | /// Schedules the continuation action for this ValueTask. 37 | public void OnCompleted( Action continuation ) 38 | { 39 | _value.AsTask().ConfigureAwait( continueOnCapturedContext: true ).GetAwaiter().OnCompleted( continuation ); 40 | } 41 | 42 | /// Schedules the continuation action for this ValueTask. 43 | public void UnsafeOnCompleted( Action continuation ) 44 | { 45 | _value.AsTask().ConfigureAwait( continueOnCapturedContext: true ).GetAwaiter().UnsafeOnCompleted( continuation ); 46 | } 47 | } 48 | } 49 | 50 | #pragma warning restore OverrideEquals // Structs should override Equals() 51 | #pragma warning restore HBStructImplementIEquatable // Structs should implement IEquatable 52 | #pragma warning restore ImplementOperatorExclamationEqualsToken // Structs should implement operator != 53 | #pragma warning restore ImplementOperatorEqualsEqualsToken // Structs should implement operator == 54 | #pragma warning restore OverrideGetHashCode // Structs should override GetHashCode() -------------------------------------------------------------------------------- /AsyncCollections/nuget/AsyncCollections.nuspec: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | AsyncCollections 5 | 1.3.8 6 | AsyncCollections 7 | $author$ 8 | https://github.com/HellBrick/AsyncCollections 9 | false 10 | $description$ 11 | 12 | v1.3.8: Re-implemented AsyncBoundedPriorityQueue 13 | v1.3.7: Fixed a race condition between AsyncBatchQueue.Add() and AsyncBatchQueue.Flush() 14 | v1.3.6: Added GitLink symbol integration 15 | v1.3.5: Fixed the cancellation delegate leak in AsyncCollection.TaskFromAny(). AsyncCollection.TakeAsync() no longer consumes an item if the CancellationToken is cancelled before the call. 16 | v1.3.4: Removed unnecessary dependencies 17 | v1.3.3: Fixed the cancellation delegate leak in AsyncCollection.TakeAsync() 18 | v1.3.2: Fixed the crash when running on Mono 19 | v1.3.1: AsyncCollection.TakeAsync() continuation can no longer be inlined on the thread that called Add() 20 | v1.3.0: AsyncCollections is now a PCL 21 | v1.2.0: AsyncBoundedPriorityQueue<T> 22 | v1.1.0: AsyncCollection<T>.TakeFromAny() 23 | v1.0.0: Initial release. 24 | 25 | async await AsyncCollection AsyncQueue 26 | 27 | 28 | 29 | 30 | 31 | -------------------------------------------------------------------------------- /AsyncCollections/packages.config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2014 HellBrick 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /NuGet.Config: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # What is it and why should I care? 2 | 3 | `AsyncCollections` is a library that contains a bunch of fast lock-free thread-safe producer-consumer collections tailored for asynchronous usage. Think `System.Collections.Concurrent` but fully `async`-ready. 4 | 5 | # Nuget package 6 | 7 | [https://www.nuget.org/packages/AsyncCollections/](https://www.nuget.org/packages/AsyncCollections/) 8 | 9 | # API 10 | 11 | ## IAsyncCollection 12 | 13 | Most of collections here implement this interface (I'd say it pretty much describes what this library is all about): 14 | 15 | ```C# 16 | public interface IAsyncCollection : IReadOnlyCollection 17 | { 18 | void Add( T item ); 19 | ValueTask TakeAsync( CancellationToken cancellationToken ); 20 | 21 | int AwaiterCount { get; } // An amount of pending item requests 22 | } 23 | ``` 24 | 25 | A copy of corefx `ValueTask` implementation is used at the moment, it's going to be replaced by the original one when 2.0 is released. 26 | 27 | ## AsyncQueue and AsyncStack 28 | 29 | These classes provide queue- and stack-based implementations of `IAsyncCollection`. 30 | 31 | ```C# 32 | AsyncQueue queue = new AsyncQueue(); 33 | ValueTask itemTask = queue.TakeAsync(); 34 | queue.Add( 42 ); // at this point itemTask completes with Result = 42 35 | 36 | AsyncStack stack = new AsyncStack(); 37 | stack.Add( 64 ); 38 | stack.Add( 128 ); 39 | int first = await stack.TakeAsync(); // 128 40 | int second = await stack.TaskAsync(); // 64 41 | ``` 42 | 43 | ## AsyncCollection 44 | 45 | Think `System.Concurrent.BlockingCollection` for `async` usage. `AsyncCollection` wraps anything that implements `IProducerConcumerCollection` and turns it into an `IAsyncCollection`: 46 | 47 | ```C# 48 | var asyncBag = new AsyncCollection( new ConcurrentBag() ); 49 | asyncBag.Add( 984 ); 50 | int item = await asyncBag.TakeAsync(); 51 | ``` 52 | 53 | Another trait it shares with `BlockingCollection` is a static `TakeFromAny` method (which is actually called `TakeFromAnyAsync` here): 54 | 55 | ```C# 56 | var bag1 = new AsyncCollection( new ConcurrentBag() ); 57 | var bag2 = new AsyncCollection( new ConcurrentBag() ); 58 | AsyncCollection[] collections = new [] { bag1, bag2 }; 59 | 60 | // Will return asynchronously when one of the queues gets an item. 61 | AnyResult result = await AsyncCollection.TakeFromAnyAsync( collections ); 62 | 63 | // Index of the collection that returned the item. 64 | int index = result.CollectionIndex; 65 | 66 | // The actual item that has been returned. 67 | int value = result.Value; 68 | ``` 69 | 70 | ## AsyncBoundedPriorityQueue 71 | 72 | This is a priority queue with a limited number of priority levels: 73 | 74 | ```C# 75 | var queue = new AsyncBoundedPriorityQueue( priorityLevels: 3 ); 76 | 77 | queue.Add( 1000 ); // item is added at the lowest priority by default 78 | queue.AddTopPriority( 42 ); 79 | queue.AddLowPriority( 999 ); 80 | queue.Add( 16, priority: 1 ); 81 | 82 | // 42 16 1000 999 83 | while ( true ) 84 | { 85 | Debug.Write( await queue.TakeAsync() ); 86 | Debug.Write( " " ); 87 | } 88 | ``` 89 | 90 | ## AsyncBatchQueue 91 | 92 | This one doesn't implement `IAsyncCollection`, because it provides a slightly different experience. Just like `AsyncQueue`, `AsyncBatchQueue` allows you to add items synchronously and retreive them asynchronously, but the difference is you consume them in batches of the specified size: 93 | 94 | ```C# 95 | AsyncBatchQueue queue = new AsyncBatchQueue( batchSize: 2 ); 96 | queue.Add( 42 ); 97 | queue.Add( 64 ); 98 | queue.Add( 128 ); 99 | 100 | // Immediately returns a batch of items 42 and 64. 101 | IReadOnlyList batch1 = await queue.TakeAsync(); 102 | 103 | // Asynchronously returns when the next batch is full 104 | // (128 and whatever the next item will be) 105 | IReadOnlyList batch2 = await queue.TakeAsync(); 106 | ``` 107 | 108 | There's a bunch of scenarios when you might want to make items available for consumption even if the specified batch size is not reached yet. First of all, you can do it manually by calling `Flush`: 109 | 110 | ```C# 111 | AsyncBatchQueue queue = new AsyncBatchQueue( 9999 ); 112 | queue.Add( 42 ); 113 | queue.Flush(); 114 | 115 | // This will return a batch of 1 item. 116 | IReadOnlyList batch = await queue.TakeAsync(); 117 | ``` 118 | 119 | Second, there's a typical case to flush pending items when a certain amount of time has passed. You can use a `WithFlushEvery` extension method to achieve this: 120 | 121 | ```C# 122 | AsyncBatchQueue queue = new AsyncBatchQueue( 10 ); 123 | 124 | using ( TimerAsyncBatchQueue timerQueue = queue.WithFlushEvery( TimeSpan.FromSeconds( 5 ) ) ) 125 | { 126 | timerQueue.Add( 42 ); 127 | 128 | // This will asynchronously return a batch of 1 item in 5 seconds. 129 | IReadOnlyList batch = await timerQueue.TakeAsync(); 130 | } 131 | 132 | // Disposing the TimerAsyncBatchQueue simply disposes the inner timer. 133 | // You can continue using the original queue though. 134 | ``` 135 | 136 | If the queue has no pending items, `Flush` won't do anything, so you don't have to worry about creating a lot of empty batches when doing manual/timer flushing. 137 | 138 | # Benchmarks 139 | 140 | The idea behind all the benchmarks is to measure how long it takes to add a fixed amount of items to the collection and them to take them back, using different amount of producer and consumer tasks. The results below were measured by using this configuration: 141 | 142 | ```ini 143 | BenchmarkDotNet=v0.9.6.0 144 | OS=Microsoft Windows NT 6.2.9200.0 145 | Processor=Intel(R) Core(TM) i5-2500 CPU @ 3.30GHz, ProcessorCount=4 146 | Frequency=3239558 ticks, Resolution=308.6841 ns, Timer=TSC 147 | HostCLR=MS.NET 4.0.30319.42000, Arch=64-bit RELEASE [RyuJIT] 148 | JitModules=clrjit-v4.6.1080.0 149 | 150 | Type=AsyncQueueBenchmark Mode=Throughput Platform=X64 151 | Jit=RyuJit LaunchCount=1 152 | ``` 153 | 154 | ## AsyncQueue benchmark 155 | 156 | There are multiple ways to achieve the functionality of `AsyncQueue`: 157 | 158 | * `AsyncQueue` itself 159 | * `AsyncCollection` that wraps `ConcurrentQueue` 160 | * `Nito.AsyncEx.AsyncCollection` (https://github.com/StephenCleary/AsyncEx) that wraps `ConcurrentQueue` 161 | * `BlockingCollection` (this isn't really asynchronous and will starve the thread pool on large consumer count, but still is an interesting thing to compare against) 162 | * `System.Threading.Tasks.Dataflow.BufferBlock` 163 | 164 | ``` 165 | Method | ConsumerTasks | ProducerTasks | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op | 166 | -------------------------------------------- |-------------- |-------------- |---------------- |-------------- |--------- |------ |------ |------------------- | 167 | AsyncQueue | 1 | 1 | 914.6110 us | 4.6121 us | 3,73 | 0,39 | - | 57 889,00 | 168 | AsyncCollection( ConcurrentQueue ) | 1 | 1 | 969.8161 us | 4.2769 us | 2,37 | - | - | 31 965,97 | 169 | Nito.AsyncEx.AsyncCollection | 1 | 1 | 67,751.7260 us | 104.9323 us | 580,78 | 0,98 | - | 7 446 934,72 | 170 | System.Concurrent.BlockingCollection | 1 | 1 | 2,108.3728 us | 17.0404 us | 2,25 | - | - | 35 318,88 | 171 | System.Threading.Tasks.Dataflow.BufferBlock | 1 | 1 | 1,673.1337 us | 7.6434 us | 19,88 | - | - | 263 489,31 | 172 | -------------------------------------------- |-------------- |-------------- |---------------- |-------------- |--------- |------ |------ |------------------- | 173 | AsyncQueue | 1 | 3 | 2,527.1599 us | 8.7526 us | 3,16 | 1,13 | - | 71 401,25 | 174 | AsyncCollection( ConcurrentQueue ) | 1 | 3 | 2,904.8245 us | 14.8086 us | 5,02 | 0,94 | - | 91 164,24 | 175 | Nito.AsyncEx.AsyncCollection | 1 | 3 | 82,357.5946 us | 2,624.8432 us | 704,33 | 36,10 | - | 10 253 908,07 | 176 | System.Concurrent.BlockingCollection | 1 | 3 | 6,337.2439 us | 64.5049 us | 4,56 | - | - | 78 099,77 | 177 | System.Threading.Tasks.Dataflow.BufferBlock | 1 | 3 | 3,738.7718 us | 48.2398 us | 90,86 | - | - | 1 192 165,45 | 178 | -------------------------------------------- |-------------- |-------------- |---------------- |-------------- |--------- |------ |------ |------------------- | 179 | AsyncQueue | 3 | 1 | 1,393.2974 us | 89.1313 us | 5,84 | 0,90 | - | 100 325,39 | 180 | AsyncCollection( ConcurrentQueue ) | 3 | 1 | 5,931.3962 us | 655.4277 us | 98,69 | 6,88 | 1,18 | 1 662 971,75 | 181 | Nito.AsyncEx.AsyncCollection | 3 | 1 | 66,368.2408 us | 83.2923 us | 559,00 | 1,00 | - | 7 175 669,84 | 182 | System.Concurrent.BlockingCollection | 3 | 1 | 2,506.8905 us | 15.4637 us | 2,09 | - | - | 29 234,91 | 183 | System.Threading.Tasks.Dataflow.BufferBlock | 3 | 1 | 1,680.4107 us | 13.3545 us | 19,07 | - | - | 273 553,48 | 184 | -------------------------------------------- |-------------- |-------------- |---------------- |-------------- |--------- |------ |------ |------------------- | 185 | AsyncQueue | 3 | 3 | 2,673.4342 us | 18.6929 us | 4,28 | 1,17 | - | 89 555,79 | 186 | AsyncCollection( ConcurrentQueue ) | 3 | 3 | 3,304.1851 us | 40.6244 us | 6,69 | 0,37 | - | 113 443,76 | 187 | Nito.AsyncEx.AsyncCollection | 3 | 3 | 249,474.1560 us | 1,340.5204 us | 1 275,00 | 1,00 | - | 16 685 767,14 | 188 | System.Concurrent.BlockingCollection | 3 | 3 | 6,826.4026 us | 99.6510 us | 4,37 | - | - | 80 584,81 | 189 | System.Threading.Tasks.Dataflow.BufferBlock | 3 | 3 | 3,901.0241 us | 55.7299 us | 90,86 | - | - | 1 243 432,93 | 190 | -------------------------------------------- |-------------- |-------------- |---------------- |-------------- |--------- |------ |------ |------------------- | 191 | ``` 192 | 193 | ## AsyncBatchQueue benchmark 194 | 195 | There are less alternatives to `AsyncBatchQueue` exist in the wild that I know of. As a matter of fact, the only thing I can come up with is `System.Threading.Tasks.Dataflow.BatchBlock`, so here it is: 196 | 197 | ``` 198 | Method | ConsumerTasks | ProducerTasks | Median | StdDev | Gen 0 | Gen 1 | Gen 2 | Bytes Allocated/Op | 199 | ------------------------------------------- |-------------- |-------------- |-------------- |----------- |------- |------ |------ |------------------- | 200 | HellBrick.AsyncCollections.AsyncBatchQueue | 1 | 1 | 581.7463 us | 3.2790 us | 125,68 | 4,50 | - | 77 281,41 | 201 | System.Threading.Tasks.Dataflow.BatchBlock | 1 | 1 | 891.3451 us | 1.4635 us | 150,34 | - | - | 91 860,30 | 202 | ------------------------------------------- |-------------- |-------------- |-------------- |----------- |------- |------ |------ |------------------- | 203 | HellBrick.AsyncCollections.AsyncBatchQueue | 1 | 3 | 1,891.6112 us | 10.6826 us | 278,83 | 12,86 | - | 178 598,12 | 204 | System.Threading.Tasks.Dataflow.BatchBlock | 1 | 3 | 2,437.1000 us | 4.6214 us | 381,67 | - | - | 230 144,44 | 205 | ------------------------------------------- |-------------- |-------------- |-------------- |----------- |------- |------ |------ |------------------- | 206 | HellBrick.AsyncCollections.AsyncBatchQueue | 3 | 1 | 891.6061 us | 2.7588 us | 145,75 | 6,90 | - | 98 127,34 | 207 | System.Threading.Tasks.Dataflow.BatchBlock | 3 | 1 | 900.5719 us | 5.4585 us | 173,48 | - | - | 110 865,66 | 208 | ------------------------------------------- |-------------- |-------------- |-------------- |----------- |------- |------ |------ |------------------- | 209 | HellBrick.AsyncCollections.AsyncBatchQueue | 3 | 3 | 2,126.5695 us | 19.0822 us | 485,00 | 18,00 | - | 316 228,75 | 210 | System.Threading.Tasks.Dataflow.BatchBlock | 3 | 3 | 2,470.9311 us | 19.1620 us | 395,91 | - | - | 242 047,11 | 211 | ------------------------------------------- |-------------- |-------------- |-------------- |----------- |------- |------ |------ |------------------- | 212 | ``` 213 | --------------------------------------------------------------------------------