From f4e82dacf10de06afcb037bcb0205a231bd1b487 Mon Sep 17 00:00:00 2001 From: stephentoub Date: Fri, 16 Oct 2015 15:23:45 -0400 Subject: [PATCH] Initial implementation of System.Threading.Tasks.Channels Prototype of a new System.Threading.Tasks.Channels library, which provides data structures for handing off data between producers and consumers. The included README.md provides an overview of the library and its exposed functionality. An initial set of functional tests are included, with ~95% line and branch coverage. Further work is desired to expose more operations over channels, provide additional built-in channel implementations, and optimize the existing channel and operator implementations further. --- src/System.Threading.Tasks.Channels/README.md | 232 ++ .../System.Threading.Tasks.Channels.sln | 28 + .../src/Resources/Strings.Designer.cs | 91 + .../src/Resources/Strings.resx | 141 ++ .../System.Threading.Tasks.Channels.csproj | 53 + .../Concurrent/SingleProducerConsumerQueue.cs | 323 +++ .../src/System/Threading/IAsyncEnumerator.cs | 20 + .../Tasks/Channels/Channel.AsyncEnumerator.cs | 53 + .../Tasks/Channels/Channel.BoundedChannel.cs | 354 +++ .../Tasks/Channels/Channel.CaseBuilder.cs | 490 ++++ .../Channels/Channel.EnumerableChannel.cs | 108 + .../Tasks/Channels/Channel.Observable.cs | 142 ++ .../Tasks/Channels/Channel.ReaderWriter.cs | 94 + .../Channels/Channel.SerializationChannel.cs | 268 ++ .../Tasks/Channels/Channel.SimpleQueue.cs | 90 + .../Channels/Channel.SpscUnboundedChannel.cs | 206 ++ .../Channels/Channel.UnboundedChannel.cs | 244 ++ .../Channels/Channel.UnbufferedChannel.cs | 288 +++ .../Threading/Tasks/Channels/Channel.cs | 268 ++ .../Threading/Tasks/Channels/IChannel.cs | 66 + .../Tasks/Channels/IDebugEnumerator.cs | 29 + .../src/System/Threading/Tasks/ValueTask.cs | 152 ++ .../src/System/Threading/Tasks/VoidResult.cs | 7 + .../src/project.json | 14 + .../src/project.lock.json | 568 +++++ .../tests/BoundedChannelTests.cs | 181 ++ .../tests/CaseBuilderTests.cs | 619 +++++ .../tests/ChannelTestBase.cs | 599 +++++ .../tests/ChannelTests.cs | 165 ++ .../tests/DebuggerAttributes.cs | 140 ++ .../tests/EnumerableChannelTests.cs | 102 + .../tests/ObservableObserverTests.cs | 47 + .../tests/SerializationChannelTests.cs | 166 ++ ...stem.Threading.Tasks.Channels.Tests.csproj | 39 + .../tests/TestBase.cs | 60 + .../tests/UnboundedChannelTests.cs | 165 ++ .../tests/UnbufferedChannelTests.cs | 118 + .../tests/ValueTaskTests.cs | 127 + .../tests/project.json | 26 + .../tests/project.lock.json | 2210 +++++++++++++++++ 40 files changed, 9093 insertions(+) create mode 100644 src/System.Threading.Tasks.Channels/README.md create mode 100644 src/System.Threading.Tasks.Channels/System.Threading.Tasks.Channels.sln create mode 100644 src/System.Threading.Tasks.Channels/src/Resources/Strings.Designer.cs create mode 100644 src/System.Threading.Tasks.Channels/src/Resources/Strings.resx create mode 100644 src/System.Threading.Tasks.Channels/src/System.Threading.Tasks.Channels.csproj create mode 100644 src/System.Threading.Tasks.Channels/src/System/Collections/Concurrent/SingleProducerConsumerQueue.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/IAsyncEnumerator.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.AsyncEnumerator.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.BoundedChannel.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.CaseBuilder.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.EnumerableChannel.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.Observable.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.ReaderWriter.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SerializationChannel.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SimpleQueue.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SpscUnboundedChannel.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.UnboundedChannel.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.UnbufferedChannel.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/IChannel.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/IDebugEnumerator.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/ValueTask.cs create mode 100644 src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/VoidResult.cs create mode 100644 src/System.Threading.Tasks.Channels/src/project.json create mode 100644 src/System.Threading.Tasks.Channels/src/project.lock.json create mode 100644 src/System.Threading.Tasks.Channels/tests/BoundedChannelTests.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/CaseBuilderTests.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/ChannelTestBase.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/ChannelTests.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/DebuggerAttributes.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/EnumerableChannelTests.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/ObservableObserverTests.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/SerializationChannelTests.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/System.Threading.Tasks.Channels.Tests.csproj create mode 100644 src/System.Threading.Tasks.Channels/tests/TestBase.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/UnboundedChannelTests.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/UnbufferedChannelTests.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/ValueTaskTests.cs create mode 100644 src/System.Threading.Tasks.Channels/tests/project.json create mode 100644 src/System.Threading.Tasks.Channels/tests/project.lock.json diff --git a/src/System.Threading.Tasks.Channels/README.md b/src/System.Threading.Tasks.Channels/README.md new file mode 100644 index 00000000000..68edb6752be --- /dev/null +++ b/src/System.Threading.Tasks.Channels/README.md @@ -0,0 +1,232 @@ +# System.Threading.Tasks.Channels + +The System.Threading.Tasks.Channels library provides a set of synchronization data structures +for passing data between producers and consumers. Whereas the existing System.Threading.Tasks.Dataflow +library is focused on pipelining and connecting together dataflow "blocks" which encapsulate both storage and +processing, System.Threading.Tasks.Channels is focused purely on the storage aspect, with data structures used +to provide the hand-offs between participants explicitly coded to use the storage. The library is designed to be +used with async/await in C#. + +## Interfaces + +The library is centered around the ```IChannel``` interface, which represents a data structure +that can have instances of ```T``` written to it and read from it. The interface is actually the +combination of a few other interfaces that represent the reading and writing halves: +```C# +public interface IChannel : IChannel { } + +public interface IChannel : IWriteableChannel, IReadableChannel { } + +public interface IReadableChannel +{ + bool TryRead(out T item); + ValueTask ReadAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken)); + Task Completion { get; } +} + +public interface IWriteableChannel +{ + bool TryWrite(T item); + Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken)); + Task WaitToWriteAsync(CancellationToken cancellationToken = default(CancellationToken)); + void Complete(Exception error = null); +} +``` +The ```IReadableChannel``` and ```IWriteableChannel``` types represent these two halves, +with the former allowing data to be read from it and the latter allowing data to be written to it. +The interfaces mirror each other, with operations exposed on each that are counterparts of those +on the other: +- ```TryRead```/```TryWrite```: Attempt to read or write an item synchronously, returning whether the read or write was successful. +- ```ReadAsync```/```WriteAsync```: Read or write an item asynchronously. +- ```WaitToReadAsync```/```WaitToWriteAsync```: Return a ```Task``` that will complete when reading or writing can be attempted. If +the task completes with a ```true``` result, at that moment the channel was available for reading or writing, though +because these channels may be used concurrently, it's possible the status changed the moment after the operation completed. +If the task completes with a ```false``` result, the channel has been completed and will not be able to satisfy a read or write. +- ```Complete```/```Completion```: Channels may be completed, such that no additional items may be written; such channels will "complete" when +marked as completed and all existing data in the channel has been consumed. Channels may also be marked as faulted by passing +an optional ```Exception``` to Complete; this exception will emerge when awaiting on the Completion Task, as well as when trying +to ```ReadAsync``` from an empty completed collection. + +## Core Channels + +Any type may implement one or more of these interfaces to be considered a channel. However, several core channels are built-in +to the library. These channels are created via factory methods on the ```Channel``` type: +```C# +public static class Channel +{ + public static IChannel Create(int bufferedCapacity = Unbounded, bool singleReaderWriter = false); + public static IChannel CreateUnbuffered(); + ... +} +``` +- ```Create```: Used to create a buffered channel. When no arguments are provided, the channel may be used concurrently +by any number of readers and writers, and is unbounded in size, limited only by available memory. An optional ```bufferedCapacity``` +may be supplied, which limits the number of items the channel may buffer to that amount; when that limit is reached, attempts to +TryWrite will return false, and WriteAsync and WaitToWriteAsync operations will not complete until space becomes available. An +optional ```singleReaderWriter``` argument may also be supplied; this is a performance optimization, whereby the developer guarantees +that at most one reader and at most one writer will use the channel at a time, in exchange for significantly lower overheads. +- ```CreateUnbuffered```: Used to create an unbuffered channel. Such a channel has no internal storage for ```T``` items, +instead pairing up writers and readers to rendezvous and directly hand off their data from one to the other. TryWrite operations +will only succeed if there's currently an outstanding ReadAsync operation, and conversely TryRead operations will only succeed if there's +currently an outstanding WriteAsync operation. ReadAsync and WriteAsync operations will complete once the counterpart operation +arrives to satisfy the request. + +## Specialized Channels + +Several additional channel types are provided to highlight the kinds of things that can be done with channels: +### Distributed Channels +```C# +public static class Channel +{ + public static IReadableChannel ReadFromStream(Stream source); + public static IWriteableChannel WriteToStream(Stream destination); + ... +} +``` +The implementation provides an example for how channels can be wrapped around System.IO.Stream, which then allows for using +the channel interfaces and things implemented in terms of them around arbitrary streams, such as those used to communicate +cross-process and cross-machine, e.g. ```PipeStream``` and ```NetworkStream```. In the current implementation, the serialization employed +is limited and rudimentary, using BinaryReader/Writer and limited to the types they support, but the design could be augmented +to support arbitrary serialization models. + +### Integration With Existing Interfaces + +```Channel``` includes a ```CreateFromEnumerable``` method that creates a channel from an enumerable: +```C# +public static class Channel +{ + public static IReadableChannel CreateFromEnumerable(IEnumerable source); + ... +} +``` +Constructing the channel gets an enumerator from the enumerable, and reading an item from the channel +pushes the enumerator forward. + +```Channel``` also includes support for going between channels and observables and observers: +```C# +public static class Channel +{ + public static IObservable AsObservable(this IReadableChannel source); + public static IObserver AsObserver(this IWriteableChannel target); + ... +} +``` +This allows for subscribing a writeable channel to an observable as an observer, and subscribing other observers +to a readable channel as an observable. With this support, IObservable-based LINQ queries can be written against +data in channels. + +## Additional Support for Reading + +Readable channels can be read from using the ```TryRead```/```ReadAsync```/```WaitForReadAsync``` methods. However, +the library provides higher-level support built on top of these operations, in order to make reading more integrated +in other scenarios. + +```IReadableChannel``` can be directly awaited. Rather than doing: +```C# +T result = await channel.ReadAsync(); +``` +code can simply do the equivalent: +```C# +T result = await channel; +``` + +The library also includes a prototype for an ```IAsyncEnumerator```, which can be used to asynchronously +read all of the data out of a channel: +```C# +IAsyncEnumerator e = channel.GetAsyncEnumerator(); +while (await e.MoveNextAsync()) +{ + Use(e.Current); +} +``` +If C# were to gain support for such async enumerators (e.g. https://github.com/dotnet/roslyn/issues/261), +such iteration could be performed using those language features, e.g. with hypothetical syntax: +```C# +foreach (await T item in channel) +{ + Use(item); +} +``` + +## Selecting + +```Channel``` also serves as an entry point for building up case-select constructs, where pairs of channels +and associated delegates are provided, with the implementation asynchronously waiting for data or space to +be available in the associated channel and then executing the associated delegate. +```C# +public static class Channel +{ + public static CaseBuilder CaseRead(IReadableChannel channel, Action action); + public static CaseBuilder CaseRead(IReadableChannel channel, Func func); + public static CaseBuilder CaseWrite(IWriteableChannel channel, T item, Action action); + public static CaseBuilder CaseWrite(IWriteableChannel channel, T item, Func func); + ... +} +``` +The ```CaseBuilder``` that's returned provides additional operations that can be chained on, providing a fluent interface: +```C# +public sealed class CaseBuilder +{ + public CaseBuilder CaseRead(IReadableChannel channel, Action action) + public CaseBuilder CaseRead(IReadableChannel channel, Func func) + + public CaseBuilder CaseWrite(IWriteableChannel channel, T item, Action action) + public CaseBuilder CaseWrite(IWriteableChannel channel, T item, Func func) + + public CaseBuilder CaseDefault(Action action) + public CaseBuilder CaseDefault(Func func) + + public Task SelectAsync(CancellationToken cancellationToken = default(CancellationToken)) + public Task SelectUntilAsync(Func conditionFunc, CancellationToken cancellationToken = default(CancellationToken)) +} +``` +For example, we can build up a case-select that will read from the first of three channels that has data available, +executing just one of their delegates, and leaving the other channels intact: +```C# +IChannel c1 = Channel.Create(); +IChannel c2 = Channel.CreateUnbuffered(); +IChannel c3 = Channel.Create(); +... +await Channel + .CaseRead(c1, i => HandleInt32(i)) + .CaseRead(c2, s => HandleString(s)) + .CaseRead(c3, d => HandleDouble(d)) + .SelectAsync(); +``` +A ```CaseDefault``` may be added for cases where some default action should be performed if the other cases +aren't satisfiable immediately. + +Additionally, as it's potentially desirable to want to select in a loop (a ```CaseBuilder``` may be reused +for multiple select operations), a ```SelectUntilAsync``` is provided that performs such a loop internally +until a particular condition is met. For example, this will read all of the data out of all of the previously +instantiated channels until all of the channels are completed: +```C# +await Channel + .CaseRead(c1, i => HandleInt32(i)) + .CaseRead(c2, s => HandleString(s)) + .CaseRead(c3, d => HandleDouble(d)) + .SelectUntilAsync(_ => true); +``` +whereas this will stop after 5 items have been processed: +```C# +await Channel + .CaseRead(c1, i => HandleInt32(i)) + .CaseRead(c2, s => HandleString(s)) + .CaseRead(c3, d => HandleDouble(d)) + .SelectUntilAsync(completions => completions < 5); +``` + +## Relationship with TPL Dataflow + +The ```BufferBlock``` type in System.Threading.Tasks.Dataflow provides functionality similar to that of ```Channel.Create()```. +However, ```BufferBlock``` is designed and optimized for use as part of dataflow chains with other dataflow blocks. The Channels +library is more focused on the specific scenario of handing data off between open-coded producers and consumers, and is optimized +for that scenario, further expanding on the kinds of such buffers available and with implementations geared towards the relevant +consumption models. + +If these Channel interfaces were to become part of corefx, System.Threading.Tasks.Dataflow would likely take a dependency on +System.Threading.Tasks.Channels as a lower-level set of abstractions, and several of the blocks in System.Threading.Tasks.Dataflow +would likely be modified to implement them as well, enabling direct integration between the libraries. For example, ```BufferBlock``` +would likely implement ```IChannel```, ```ActionBlock``` would likely implement ```IWriteableChannel```, +```TransformBlock``` would likely implement ```IChannel```, etc. diff --git a/src/System.Threading.Tasks.Channels/System.Threading.Tasks.Channels.sln b/src/System.Threading.Tasks.Channels/System.Threading.Tasks.Channels.sln new file mode 100644 index 00000000000..60c71280dda --- /dev/null +++ b/src/System.Threading.Tasks.Channels/System.Threading.Tasks.Channels.sln @@ -0,0 +1,28 @@ + +Microsoft Visual Studio Solution File, Format Version 12.00 +# Visual Studio 14 +VisualStudioVersion = 14.0.23107.0 +MinimumVisualStudioVersion = 10.0.40219.1 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Threading.Tasks.Channels.Tests", "tests\System.Threading.Tasks.Channels.Tests.csproj", "{DD942FDB-184A-4D9F-8A1E-3D531858A4B1}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Threading.Tasks.Channels", "src\System.Threading.Tasks.Channels.csproj", "{C5FD8740-19EA-4BCC-B518-7F16B55D23CA}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {DD942FDB-184A-4D9F-8A1E-3D531858A4B1}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {DD942FDB-184A-4D9F-8A1E-3D531858A4B1}.Debug|Any CPU.Build.0 = Debug|Any CPU + {DD942FDB-184A-4D9F-8A1E-3D531858A4B1}.Release|Any CPU.ActiveCfg = Debug|Any CPU + {DD942FDB-184A-4D9F-8A1E-3D531858A4B1}.Release|Any CPU.Build.0 = Debug|Any CPU + {C5FD8740-19EA-4BCC-B518-7F16B55D23CA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {C5FD8740-19EA-4BCC-B518-7F16B55D23CA}.Debug|Any CPU.Build.0 = Debug|Any CPU + {C5FD8740-19EA-4BCC-B518-7F16B55D23CA}.Release|Any CPU.ActiveCfg = Release|Any CPU + {C5FD8740-19EA-4BCC-B518-7F16B55D23CA}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection +EndGlobal diff --git a/src/System.Threading.Tasks.Channels/src/Resources/Strings.Designer.cs b/src/System.Threading.Tasks.Channels/src/Resources/Strings.Designer.cs new file mode 100644 index 00000000000..bd1e027d1f2 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/Resources/Strings.Designer.cs @@ -0,0 +1,91 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.0 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace System.Text.Formatting { + using System; + using System.Reflection; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "4.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Strings { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Strings() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("Resources.Strings", typeof(Strings).GetTypeInfo().Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Invalid format. + /// + internal static string InvalidFormat { + get { + return ResourceManager.GetString("InvalidFormat", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Precision value exceeds maximum allowed. + /// + internal static string PrecisionValueOutOfRange { + get { + return ResourceManager.GetString("PrecisionValueOutOfRange", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Unable to parse precision specification. + /// + internal static string UnableToParsePrecision { + get { + return ResourceManager.GetString("UnableToParsePrecision", resourceCulture); + } + } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/Resources/Strings.resx b/src/System.Threading.Tasks.Channels/src/Resources/Strings.resx new file mode 100644 index 00000000000..4838e39e528 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/Resources/Strings.resx @@ -0,0 +1,141 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Destination stream is not writable. + + + Source stream is not readable + + + The channel has been closed. + + + Result not available. + + + A default case already exists. + + + The specified type cannot be serialized or deserialized. + + + Concurrent operations may not be issued against a single reader/writer channel. + + \ No newline at end of file diff --git a/src/System.Threading.Tasks.Channels/src/System.Threading.Tasks.Channels.csproj b/src/System.Threading.Tasks.Channels/src/System.Threading.Tasks.Channels.csproj new file mode 100644 index 00000000000..cbd46a2d1bd --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System.Threading.Tasks.Channels.csproj @@ -0,0 +1,53 @@ + + + + + Debug + AnyCPU + Library + System.Threading.Tasks.Channels + System.Threading.Tasks.Channels + 4.0.0.0 + true + {C5FD8740-19EA-4BCC-B518-7F16B55D23CA} + + + v5.0 + + .NETPlatform,Version=v5.0 + + + + ..\..\..\bin\Windows_NT.AnyCPU.Debug\System.Threading.Tasks.Channels\System.Threading.Tasks.Channels.xml + + + ..\..\..\bin\Windows_NT.AnyCPU.Release\System.Threading.Tasks.Channels\System.Threading.Tasks.Channels.xml + + + + + + + + + + + + + + + + + + + + + + + + README.md + + + + + \ No newline at end of file diff --git a/src/System.Threading.Tasks.Channels/src/System/Collections/Concurrent/SingleProducerConsumerQueue.cs b/src/System.Threading.Tasks.Channels/src/System/Collections/Concurrent/SingleProducerConsumerQueue.cs new file mode 100644 index 00000000000..8961450ad4c --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Collections/Concurrent/SingleProducerConsumerQueue.cs @@ -0,0 +1,323 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.Runtime.InteropServices; +using System.Threading; + +namespace System.Collections.Concurrent +{ + /// + /// Provides a producer/consumer queue safe to be used by only one producer and one consumer concurrently. + /// + /// Specifies the type of data contained in the queue. + [DebuggerDisplay("Count = {Count}")] + [DebuggerTypeProxy(typeof(SingleProducerSingleConsumerQueue<>.SingleProducerSingleConsumerQueue_DebugView))] + internal sealed class SingleProducerSingleConsumerQueue + { + // Design: + // + // SingleProducerSingleConsumerQueue (SPSCQueue) is a concurrent queue designed to be used + // by one producer thread and one consumer thread. SPSCQueue does not work correctly when used by + // multiple producer threads concurrently or multiple consumer threads concurrently. + // + // SPSCQueue is based on segments that behave like circular buffers. Each circular buffer is represented + // as an array with two indexes: _first and _last. _first is the index of the array slot for the consumer + // to read next, and _last is the slot for the producer to write next. The circular buffer is empty when + // (_first == _last), and full when ((_last+1) % _array.Length == _first). + // + // Since _first is only ever modified by the consumer thread and _last by the producer, the two indices can + // be updated without interlocked operations. As long as the queue size fits inside a single circular buffer, + // enqueues and dequeues simply advance the corresponding indices around the circular buffer. If an enqueue finds + // that there is no room in the existing buffer, however, a new circular buffer is allocated that is twice as big + // as the old buffer. From then on, the producer will insert values into the new buffer. The consumer will first + // empty out the old buffer and only then follow the producer into the new (larger) buffer. + // + // As described above, the enqueue operation on the fast path only modifies the _first field of the current segment. + // However, it also needs to read _last in order to verify that there is room in the current segment. Similarly, the + // dequeue operation on the fast path only needs to modify _last, but also needs to read _first to verify that the + // queue is non-empty. This results in true cache line sharing between the producer and the consumer. + // + // The cache line sharing issue can be mitigating by having a possibly stale copy of _first that is owned by the producer, + // and a possibly stale copy of _last that is owned by the consumer. So, the consumer state is described using + // (_first, _lastCopy) and the producer state using (_firstCopy, _last). The consumer state is separated from + // the producer state by padding, which allows fast-path enqueues and dequeues from hitting shared cache lines. + // _lastCopy is the consumer's copy of _last. Whenever the consumer can tell that there is room in the buffer + // simply by observing _lastCopy, the consumer thread does not need to read _last and thus encounter a cache miss. Only + // when the buffer appears to be empty will the consumer refresh _lastCopy from _last. _firstCopy is used by the producer + // in the same way to avoid reading _first on the hot path. + + /// The initial size to use for segments (in number of elements). + private const int INIT_SEGMENT_SIZE = 32; // must be a power of 2 + /// The maximum size to use for segments (in number of elements). + private const int MAX_SEGMENT_SIZE = 0x1000000; // this could be made as large as Int32.MaxValue / 2 + + /// The head of the linked list of segments. + private volatile Segment _head; + /// The tail of the linked list of segments. + private volatile Segment _tail; + + /// Initializes the queue. + internal SingleProducerSingleConsumerQueue() + { + // Validate constants in ctor rather than in an explicit cctor that would cause perf degradation + Debug.Assert(INIT_SEGMENT_SIZE > 0, "Initial segment size must be > 0."); + Debug.Assert((INIT_SEGMENT_SIZE & (INIT_SEGMENT_SIZE - 1)) == 0, "Initial segment size must be a power of 2"); + Debug.Assert(INIT_SEGMENT_SIZE <= MAX_SEGMENT_SIZE, "Initial segment size should be <= maximum."); + Debug.Assert(MAX_SEGMENT_SIZE < Int32.MaxValue / 2, "Max segment size * 2 must be < Int32.MaxValue, or else overflow could occur."); + + // Initialize the queue + _head = _tail = new Segment(INIT_SEGMENT_SIZE); + } + + /// Enqueues an item into the queue. + /// The item to enqueue. + public void Enqueue(T item) + { + Segment segment = _tail; + T[] array = segment._array; + int last = segment._state._last; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously room in the current segment + int tail2 = (last + 1) & (array.Length - 1); + if (tail2 != segment._state._firstCopy) + { + array[last] = item; + segment._state._last = tail2; + } + // Slow path: there may not be room in the current segment. + else EnqueueSlow(item, ref segment); + } + + /// Enqueues an item into the queue. + /// The item to enqueue. + /// The segment in which to first attempt to store the item. + private void EnqueueSlow(T item, ref Segment segment) + { + Debug.Assert(segment != null, "Expected a non-null segment."); + + if (segment._state._firstCopy != segment._state._first) + { + segment._state._firstCopy = segment._state._first; + Enqueue(item); // will only recur once for this enqueue operation + return; + } + + int newSegmentSize = _tail._array.Length << 1; // double size + Debug.Assert(newSegmentSize > 0, "The max size should always be small enough that we don't overflow."); + if (newSegmentSize > MAX_SEGMENT_SIZE) newSegmentSize = MAX_SEGMENT_SIZE; + + var newSegment = new Segment(newSegmentSize); + newSegment._array[0] = item; + newSegment._state._last = 1; + newSegment._state._lastCopy = 1; + + try { } + finally + { + // Finally block to protect against corruption due to a thread abort + // between setting _next and setting _tail. + Volatile.Write(ref _tail._next, newSegment); // ensure segment not published until item is fully stored + _tail = newSegment; + } + } + + /// Attempts to dequeue an item from the queue. + /// The dequeued item. + /// true if an item could be dequeued; otherwise, false. + public bool TryDequeue(out T result) + { + Segment segment = _head; + T[] array = segment._array; + int first = segment._state._first; // local copy to avoid multiple volatile reads + + // Fast path: there's obviously data available in the current segment + if (first != segment._state._lastCopy) + { + result = array[first]; + array[first] = default(T); // Clear the slot to release the element + segment._state._first = (first + 1) & (array.Length - 1); + return true; + } + // Slow path: there may not be data available in the current segment + else return TryDequeueSlow(ref segment, ref array, out result); + } + + /// Attempts to dequeue an item from the queue. + /// The array from which the item was dequeued. + /// The segment from which the item was dequeued. + /// The dequeued item. + /// true if an item could be dequeued; otherwise, false. + private bool TryDequeueSlow(ref Segment segment, ref T[] array, out T result) + { + Debug.Assert(segment != null, "Expected a non-null segment."); + Debug.Assert(array != null, "Expected a non-null item array."); + + if (segment._state._last != segment._state._lastCopy) + { + segment._state._lastCopy = segment._state._last; + return TryDequeue(out result); // will only recur once for this dequeue operation + } + + if (segment._next != null && segment._state._first == segment._state._last) + { + segment = segment._next; + array = segment._array; + _head = segment; + } + + int first = segment._state._first; // local copy to avoid extraneous volatile reads + + if (first == segment._state._last) + { + result = default(T); + return false; + } + + result = array[first]; + array[first] = default(T); // Clear the slot to release the element + segment._state._first = (first + 1) & (segment._array.Length - 1); + segment._state._lastCopy = segment._state._last; // Refresh _lastCopy to ensure that _first has not passed _lastCopy + + return true; + } + + /// Gets whether the collection is currently empty. + public bool IsEmpty + { + // This implementation is optimized for calls from the consumer. + get + { + Segment head = _head; + if (head._state._first != head._state._lastCopy) return false; // _first is volatile, so the read of _lastCopy cannot get reordered + if (head._state._first != head._state._last) return false; + return head._next == null; + } + } + + /// Gets an enumerable for the collection. + /// WARNING: This should only be used for debugging purposes. It is not safe to be used concurrently. + public IEnumerator GetEnumerator() + { + for (Segment segment = _head; segment != null; segment = segment._next) + { + for (int pt = segment._state._first; + pt != segment._state._last; + pt = (pt + 1) & (segment._array.Length - 1)) + { + yield return segment._array[pt]; + } + } + } + + /// Gets the number of items in the collection. + /// WARNING: This should only be used for debugging purposes. It is not meant to be used concurrently. + internal int Count + { + get + { + int count = 0; + for (Segment segment = _head; segment != null; segment = segment._next) + { + int arraySize = segment._array.Length; + int first, last; + while (true) // Count is not meant to be used concurrently, but this helps to avoid issues if it is + { + first = segment._state._first; + last = segment._state._last; + if (first == segment._state._first) break; + } + count += (last - first) & (arraySize - 1); + } + return count; + } + } + + /// A segment in the queue containing one or more items. + [StructLayout(LayoutKind.Sequential)] + private sealed class Segment + { + /// The next segment in the linked list of segments. + internal Segment _next; + /// The data stored in this segment. + internal readonly T[] _array; + /// Details about the segment. + internal SegmentState _state; // separated out to enable StructLayout attribute to take effect + + /// Initializes the segment. + /// The size to use for this segment. + internal Segment(int size) + { + Debug.Assert((size & (size - 1)) == 0, "Size must be a power of 2"); + _array = new T[size]; + } + } + + /// Stores information about a segment. + [StructLayout(LayoutKind.Sequential)] // enforce layout so that padding reduces false sharing + private struct SegmentState + { + /// Padding to reduce false sharing between the segment's array and _first. + internal PaddingFor32 _pad0; + + /// The index of the current head in the segment. + internal volatile int _first; + /// A copy of the current tail index. + internal int _lastCopy; // not volatile as read and written by the producer, except for IsEmpty, and there _lastCopy is only read after reading the volatile _first + + /// Padding to reduce false sharing between the first and last. + internal PaddingFor32 _pad1; + + /// A copy of the current head index. + internal int _firstCopy; // not volatile as only read and written by the consumer thread + /// The index of the current tail in the segment. + internal volatile int _last; + + /// Padding to reduce false sharing with the last and what's after the segment. + internal PaddingFor32 _pad2; + } + + /// Debugger type proxy for a SingleProducerSingleConsumerQueue of T. + private sealed class SingleProducerSingleConsumerQueue_DebugView + { + /// The queue being visualized. + private readonly SingleProducerSingleConsumerQueue _queue; + + /// Initializes the debug view. + /// The queue being debugged. + public SingleProducerSingleConsumerQueue_DebugView(SingleProducerSingleConsumerQueue queue) + { + Debug.Assert(queue != null, "Expected a non-null queue."); + _queue = queue; + } + + /// Gets the contents of the list. + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items + { + get + { + List list = new List(); + foreach (T item in _queue) + list.Add(item); + return list.ToArray(); + } + } + } + } + + + /// A placeholder class for common padding constants and eventually routines. + static class PaddingHelpers + { + /// A size greater than or equal to the size of the most common CPU cache lines. + internal const int CACHE_LINE_SIZE = 128; + } + + /// Padding structure used to minimize false sharing in SingleProducerSingleConsumerQueue{T}. + [StructLayout(LayoutKind.Explicit, Size = PaddingHelpers.CACHE_LINE_SIZE - sizeof(Int32))] // Based on common case of 64-byte cache lines + struct PaddingFor32 + { + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/IAsyncEnumerator.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/IAsyncEnumerator.cs new file mode 100644 index 00000000000..49215df0852 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/IAsyncEnumerator.cs @@ -0,0 +1,20 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Threading.Tasks.Channels +{ + /// Represents an enumerator accessed asynchronously. + /// Specifies the type of the data enumerated. + public interface IAsyncEnumerator + { + /// Asynchronously move the enumerator to the next element. + /// + /// A task that returns true if the enumerator was successfully advanced to the next item, + /// or false if no more data was available in the collection. + /// + Task MoveNextAsync(); + + /// Gets the current element being enumerated. + T Current { get; } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.AsyncEnumerator.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.AsyncEnumerator.cs new file mode 100644 index 00000000000..9981ad8951b --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.AsyncEnumerator.cs @@ -0,0 +1,53 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + /// Provides an async enumerator for the data in a channel. + private sealed class AsyncEnumerator : IAsyncEnumerator + { + /// The channel being enumerated. + private readonly IReadableChannel _channel; + /// Cancellation token used to cancel the enumeration. + private readonly CancellationToken _cancellationToken; + /// The current element of the enumeration. + private T _current; + + internal AsyncEnumerator(IReadableChannel channel, CancellationToken cancellationToken) + { + _channel = channel; + _cancellationToken = cancellationToken; + } + + public T Current { get { return _current; } } + + public Task MoveNextAsync() + { + ValueTask result = _channel.ReadAsync(_cancellationToken); + + if (result.IsRanToCompletion) + { + _current = result.Result; + return s_trueTask; + } + + return result.AsTask().ContinueWith((t, s) => + { + AsyncEnumerator thisRef = (AsyncEnumerator)s; + try + { + thisRef._current = t.GetAwaiter().GetResult(); + return true; + } + catch (ClosedChannelException) + { + return false; + } + }, this, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + + } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.BoundedChannel.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.BoundedChannel.cs new file mode 100644 index 00000000000..e9e3a48bad9 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.BoundedChannel.cs @@ -0,0 +1,354 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + /// Provides a channel with a bounded capacity. + [DebuggerDisplay("Items={ItemsCountForDebugger}, Capacity={_bufferedCapacity}")] + [DebuggerTypeProxy(typeof(DebugEnumeratorDebugView<>))] + private sealed class BoundedChannel : IChannel, IDebugEnumerable + { + /// Task signaled when the channel has completed. + private readonly TaskCompletionSource _completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + /// The maximum capacity of the channel. + private readonly int _bufferedCapacity; + /// Items currently stored in the channel waiting to be read. + private readonly SimpleQueue _items = new SimpleQueue(); + /// Readers waiting to read from the channel. + private readonly SimpleQueue> _blockedReaders = new SimpleQueue>(); + /// Writers waiting to write to the channel. + private readonly SimpleQueue> _blockedWriters = new SimpleQueue>(); + /// Task signaled when any WaitToReadAsync waiters should be woken up. + private readonly SimpleQueue> _waitingReaders = new SimpleQueue>(); + /// Task signaled when any WaitToWriteAsync waiters should be woken up. + private readonly SimpleQueue> _waitingWriters = new SimpleQueue>(); + /// Set to non-null once Complete has been called. + private Exception _doneWriting; + /// Gets an object used to synchronize all state on the instance. + private object SyncObj { get { return _items; } } + + /// Initializes the . + /// The positive bounded capacity for the channel. + internal BoundedChannel(int bufferedCapacity) + { + Debug.Assert(bufferedCapacity > 0); + _bufferedCapacity = bufferedCapacity; + } + + public Task Completion { get { return _completion.Task; } } + + [Conditional("DEBUG")] + private void AssertInvariants() + { + Debug.Assert(SyncObj != null, "The sync obj must not be null."); + Debug.Assert(Monitor.IsEntered(SyncObj), "Invariants can only be validated while holding the lock."); + + if (_items.Count > 0) + { + Debug.Assert(_blockedReaders.Count == 0, "There are items available, so there shouldn't be any blocked readers."); + Debug.Assert(_waitingReaders.Count == 0, "There are items available, so there shouldn't be any waiting readers."); + } + if (_items.Count < _bufferedCapacity) + { + Debug.Assert(_blockedWriters.Count == 0, "There's space available, so there shouldn't be any blocked writers."); + Debug.Assert(_waitingWriters.Count == 0, "There's space available, so there shouldn't be any waiting writers."); + } + if (_blockedReaders.Count > 0) + { + Debug.Assert(_items.Count == 0, "There shouldn't be queued items if there's a blocked reader."); + Debug.Assert(_blockedWriters.Count == 0, "There shouldn't be any blocked writer if there's a blocked reader."); + } + if (_blockedWriters.Count > 0) + { + Debug.Assert(_items.Count == _bufferedCapacity, "We should have a full buffer if there's a blocked writer."); + Debug.Assert(_blockedReaders.Count == 0, "There shouldn't be any blocked readers if there's a blocked writer."); + } + if (_doneWriting != null || _completion.Task.IsCompleted) + { + Debug.Assert(_blockedWriters.Count == 0, "We're done writing, so there shouldn't be any blocked writers."); + Debug.Assert(_blockedReaders.Count == 0, "We're done writing, so there shouldn't be any blocked readers."); + Debug.Assert(_waitingReaders.Count == 0, "We're done writing, so any reader should have woken up."); + Debug.Assert(_waitingWriters.Count == 0, "We're done writing, so any writer should have woken up."); + } + if (_completion.Task.IsCompleted) + { + Debug.Assert(_doneWriting != null, "We can only complete if we're done writing."); + } + } + + public void Complete(Exception error = null) + { + lock (SyncObj) + { + AssertInvariants(); + + // Mark the channel as done for writing. This may only be done once. + if (_doneWriting != null) throw CreateInvalidCompletionException(); + _doneWriting = error ?? s_doneWritingSentinel; + + // If there are no items in the channel, then there's no more work to be done, + // so we complete the completion task. + if (_items.Count == 0) + { + CompleteWithOptionalError(_completion, error); + } + + // If there are any waiting readers, fail them all, as they'll now never be satisfied. + while (_blockedReaders.Count > 0) + { + var reader = _blockedReaders.Dequeue(); + reader.Fail(error ?? CreateInvalidCompletionException()); + } + + // If there are any waiting writers, fail them all, as they shouldn't be writing + // now that we're complete for writing. + while (_blockedWriters.Count > 0) + { + var writer = _blockedWriters.Dequeue(); + writer.Fail(CreateInvalidCompletionException()); + } + + // If there are any pending WaitToRead/WriteAsync calls, wake them up. + WakeUpWaiters(_waitingReaders, false); + WakeUpWaiters(_waitingWriters, false); + } + } + + public ValueTask ReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + // Fast-path cancellation check + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + AssertInvariants(); + + // If there are any items, hand one back. + if (_items.Count > 0) + { + return DequeueItemAndPostProcess(); + } + + // There weren't any items. If we're done writing so that there + // will never be more items, fail. + if (_doneWriting != null) + { + return Task.FromException(_doneWriting != s_doneWritingSentinel ? _doneWriting : CreateInvalidCompletionException()); + } + + // Otherwise, queue the reader. + var reader = Reader.Create(cancellationToken); + _blockedReaders.Enqueue(reader); + return reader.Task; + } + } + + public Task WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + AssertInvariants(); + + // If there are any items available, a read is possible. + if (_items.Count > 0) + { + return s_trueTask; + } + + // There were no items available, so if we're done writing, a read will never be possible. + if (_doneWriting != null) + { + return s_falseTask; + } + + // There were no items available, but there could be in the future, so ensure + // there's a blocked reader task and return it. + var r = Reader.Create(cancellationToken); + _waitingReaders.Enqueue(r); + return r.Task; + } + } + + public bool TryRead(out T item) + { + lock (SyncObj) + { + AssertInvariants(); + + // Get an item if there is one. + if (_items.Count > 0) + { + item = DequeueItemAndPostProcess(); + return true; + } + } + item = default(T); + return false; + } + + /// Dequeues an item, and then fixes up our state around writers and completion. + /// The dequeued item. + private T DequeueItemAndPostProcess() + { + Debug.Assert(Monitor.IsEntered(SyncObj)); + + // Dequeue an item. + T item = _items.Dequeue(); + + // If we're now empty and we're done writing, complete the channel. + if (_doneWriting != null && _items.Count == 0) + { + CompleteWithOptionalError(_completion, _doneWriting); + } + + // If there are any writers blocked, there's now room for at least one + // to be promoted to have its item moved into the items queue. We need + // to loop while trying to complete the writer in order to find one that + // hasn't yet been canceled (canceled writers transition to canceled but + // remain in the physical queue). + while (_blockedWriters.Count > 0) + { + Writer w = _blockedWriters.Dequeue(); + if (w.Success(default(VoidResult))) + { + _items.Enqueue(w.Item); + return item; + } + } + + // There was no blocked writer, so see if there's a WaitToWriteAsync + // we should wake up. + WakeUpWaiters(_waitingWriters, true); + + // Return the item + return item; + } + + public bool TryWrite(T item) + { + lock (SyncObj) + { + AssertInvariants(); + + // If we're done writing, nothing more to do. + if (_doneWriting != null) + { + return false; + } + + // If there are any blocked readers, find one that's not canceled + // and complete it with the item from this writer. + while (_blockedReaders.Count > 0) + { + Reader r = _blockedReaders.Dequeue(); + if (r.Success(item)) + { + return true; + } + } + + // If we're full, we can't write. + if (_items.Count == _bufferedCapacity) + { + return false; + } + + // There weren't any blocked (and non-canceled) readers, and + // there's room in the queue. Queue item, and let any waiting + // readers know they could try to read. + _items.Enqueue(item); + WakeUpWaiters(_waitingReaders, true); + return true; + } + } + + public Task WaitToWriteAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + AssertInvariants(); + + // If we're done writing, no writes will ever succeed. + if (_doneWriting != null) + { + return s_falseTask; + } + + // If there's space to write, a write is possible. + if (_items.Count < _bufferedCapacity) + return s_trueTask; + + // We're still allowed to write, but there's no space, + // so ensure a waiter is queued and return it. + var w = Reader.Create(cancellationToken); + _waitingWriters.Enqueue(w); + return w.Task; + } + } + + public Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + AssertInvariants(); + + // If we're done writing, trying to write is an error. + if (_doneWriting != null) + { + throw CreateInvalidCompletionException(); + } + + // If there are any blocked readers, find a non-canceled + // one to transfer the item to. + while (_blockedReaders.Count > 0) + { + Reader r = _blockedReaders.Dequeue(); + if (r.Success(item)) + { + return s_trueTask; + } + } + + // There were no available readers. If there's + // room, simply store the item. + if (_items.Count < _bufferedCapacity) + { + _items.Enqueue(item); + WakeUpWaiters(_waitingReaders, true); + return s_trueTask; + } + + // There were no readers and there was no room. + // Queue the writer. + var writer = Writer.Create(cancellationToken, item); + _blockedWriters.Enqueue(writer); + return writer.Task; + } + } + + /// Gets the number of items in the channel. This should only be used by the debugger. + private int ItemsCountForDebugger { get { return _items.Count; } } + + /// Gets an enumerator the debugger can use to show the contents of the channel. + IEnumerator IDebugEnumerable.GetEnumerator() + { + return _items.GetEnumerator(); + } + } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.CaseBuilder.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.CaseBuilder.cs new file mode 100644 index 00000000000..8efb5290406 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.CaseBuilder.cs @@ -0,0 +1,490 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + /// Provides a mutable builder for building up a case-select construct. + public sealed class CaseBuilder + { + /// The read/write cases created in this builder. + private readonly List _cases = new List(); + /// The default case, or null if there is none. + private Case _default; + + /// Initialize the builder. + internal CaseBuilder() { } + + /// + /// Adds a channel read case to the builder. If this case wins due to successfully + /// being able to read data from the channel, the specified action will be invoked + /// with the read data. + /// + /// Specifies the type of data in the channel. + /// The channel from which to read. + /// The synchronous action to invoke with an element read from the channel. + /// This builder. + public CaseBuilder CaseRead(IReadableChannel channel, Action action) + { + if (channel == null) + throw new ArgumentNullException("channel"); + if (action == null) + throw new ArgumentNullException("action"); + _cases.Add(new SyncReadCase(channel, action)); + return this; + } + + /// + /// Adds a channel read case to the builder. If this case wins due to successfully + /// being able to read data from the channel, the specified function will be invoked + /// with the read data. + /// + /// Specifies the type of data in the channel. + /// The channel from which to read. + /// The asynchronous function to invoke with an element read from the channel. + /// This builder. + public CaseBuilder CaseRead(IReadableChannel channel, Func func) + { + if (channel == null) + throw new ArgumentNullException("channel"); + if (func == null) + throw new ArgumentNullException("func"); + _cases.Add(new AsyncReadCase(channel, func)); + return this; + } + + /// + /// Adds a channel write case to the builder. If this case wins due to successfully + /// being able to write data to the channel, the specified action will be invoked. + /// + /// Specifies the type of data in the channel. + /// The channel to which to write. + /// The data to write to the channel. + /// The synchronous action to invoke once the write is successful. + /// This builder. + public CaseBuilder CaseWrite(IWriteableChannel channel, T item, Action action) + { + if (channel == null) + throw new ArgumentNullException("channel"); + if (action == null) + throw new ArgumentNullException("action"); + _cases.Add(new SyncWriteCase(channel, item, action)); + return this; + } + + /// + /// Adds a channel write case to the builder. If this case wins due to successfully + /// being able to write data to the channel, the specified action will be invoked. + /// + /// Specifies the type of data in the channel. + /// The channel to which to write. + /// The data to write to the channel. + /// The asynchronous function to invoke once the write is successful. + /// This builder. + public CaseBuilder CaseWrite(IWriteableChannel channel, T item, Func func) + { + if (channel == null) + throw new ArgumentNullException("channel"); + if (func == null) + throw new ArgumentNullException("func"); + _cases.Add(new AsyncWriteCase(channel, item, func)); + return this; + } + + /// + /// Adds a default case to this builder. This case will win if no other + /// cases are satisfied. + /// + /// The action to invoke if no other cases can be satisfied. + /// This builder. + public CaseBuilder CaseDefault(Action action) + { + if (action == null) + throw new ArgumentNullException("action"); + if (_default != null) + throw new InvalidOperationException(SR.InvalidOperationException_DefaultCaseAlreadyExists); + _default = new SyncDefaultCase(action); + return this; + } + + /// + /// Adds a default case to this builder. This case will win if no other + /// cases are satisfied. + /// + /// The asynchronous function to invoke if no other cases can be satisfied. + /// This builder. + public CaseBuilder CaseDefault(Func func) + { + if (func == null) + throw new ArgumentNullException("func"); + if (_default != null) + throw new InvalidOperationException(SR.InvalidOperationException_DefaultCaseAlreadyExists); + _default = new AsyncDefaultCase(func); + return this; + } + + /// + /// Invokes the select operation repeatedly until either the condition returns false, + /// the channels associated with all of the cases are completed, cancellation is requested, + /// or a failure occurs. + /// + /// + /// The predicate to invoke to determine whether to continue processing. It is provided with + /// the number of successful selections completed thus far, and returns whether to continue processing. + /// + /// The cancellation token to use to request cancellation of the operation. + /// + /// A task that represents the asynchronous select operation. + /// It will complete with the number of cases successfully completed. + /// + public Task SelectUntilAsync(Func conditionFunc, CancellationToken cancellationToken = default(CancellationToken)) + { + if (conditionFunc == null) + throw new ArgumentNullException("conditionFunc"); + return SelectUntilAsyncCore(conditionFunc, cancellationToken); + } + + /// Core of the SelectUntilAsync operation. + private async Task SelectUntilAsyncCore(Func condition, CancellationToken cancellationToken = default(CancellationToken)) + { + // TODO: This can be optimized further, by reusing WaitForReadAsync tasks across iterations + // rather than getting a new one per iteration, by processing multiple completed-as-true WaitForReadTasks + // as part of a single iteration, etc. For now, however, this provides the core functionality. + + int completions = 0; + while (condition(completions) && await SelectAsync(cancellationToken).ConfigureAwait(false)) + { + completions++; + } + return completions; + } + + /// + /// Invokes the select operation. If any cases can be satisfied immediately, + /// their corresponding actions will be invoked synchronously and the returned + /// task will complete synchronously. Otherwise, the first channel that + /// asynchronously satisifes its case will have its action invoked asynchronously. + /// + /// The cancellation token to use to request cancellation of the operation. + /// + /// A task that represents the asynchronous select operation. + /// It will complete with a result of true if one of the cases was satisfied, + /// or else a result of false if none of the cases could be satisfied. + /// + public Task SelectAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + // First try each case to see if it can be satisfied synchronously + foreach (Case c in _cases) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + Task t = c.TryInvokeAsync(); + if (!t.IsCompleted || t.IsFaulted || (t.Status == TaskStatus.RanToCompletion && t.Result)) + { + // If the task is faulted, then we invoked it's handling code and it failed + // so we should return the task to the caller. + + // If the task succeeded and returned true, then we successfully processed the case + // and should return the task to the caller. + + // And if the task isn't completed, since TryRead/Write are synchronous, + // this must only not have completed synchronously if we successfully "got" + // the case and invoked an asynchronous processing function, so we can + // just return the task to represent that. + return t; + } + + // Otherwise, the task either returned false or was canceled, in which case + // we keep going. + } + + // No cases could be satisfied. If there's a default case, use that. + if (_default != null) + { + return _default.TryInvokeAsync(); + } + + // No default case, and no channels were available. Run asynchronously. + return SelectAsyncCore(cancellationToken); + } + + /// Core of the SelectAsync operation. + private async Task SelectAsyncCore(CancellationToken cancellationToken = default(CancellationToken)) + { + // Loop until cancellation occurs or a case is available + Task[] tasks = new Task[_cases.Count]; + while (_cases.Count > 0) + { + // Create a cancellation token that will be canceled either when the incoming token is canceled + // or once the await finishes. When one task from one channel finishes, we don't want to leave + // tasks from other channels uncanceled. + Task completedTask; + using (var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken, default(CancellationToken))) + { + for (int i = 0; i < tasks.Length; i++) + { + tasks[i] = _cases[i].WaitAsync(cts.Token); + } + + completedTask = await Task.WhenAny(tasks).ConfigureAwait(false); + await Task.Yield(); + + cts.Cancel(); + cancellationToken.ThrowIfCancellationRequested(); + } + + // Get the completed task's result, propagating any exceptions if any occurred + Debug.Assert(completedTask.IsCompleted); + bool result = completedTask.GetAwaiter().GetResult(); + + // Get the index of the case associated with the completed task + int completedIndex = Array.IndexOf(tasks, completedTask); + Debug.Assert(completedIndex >= 0); + Debug.Assert(completedIndex < _cases.Count); + + // If the task completed successfully and with a true result, + // then we should try to invoke its case and be done if it succeeds. + if (result) + { + if (await _cases[completedIndex].TryInvokeAsync().ConfigureAwait(false)) + { + return true; + } + } + else + { + // Otherwise, this channel is done and we should remove it so as not + // to bother with it anymore. + _cases.RemoveAt(completedIndex); + tasks = new Task[_cases.Count]; + } + } + + // Ran out of cases + return false; + } + + /// Provides a base class for all cases. + private abstract class Case + { + /// Tries to invoke the case. + /// true if the case executed successfully; otherwise, false if it wasn't ready. + public abstract Task TryInvokeAsync(); + + /// Waits for the case to be available. + /// The cancellation token to use for the operation. + /// A task representing the asynchronous wait. + public virtual Task WaitAsync(CancellationToken cancellationToken) { return s_falseTask; } + } + + /// Provides the concrete case used for channel reads with synchronous processing. + private sealed class SyncReadCase : Case + { + private readonly IReadableChannel _channel; + private readonly Action _action; + + internal SyncReadCase(IReadableChannel channel, Action action) + { + _channel = channel; + _action = action; + } + + public override Task WaitAsync(CancellationToken cancellationToken) + { + return _channel.WaitToReadAsync(cancellationToken); + } + + public override Task TryInvokeAsync() + { + T item; + if (_channel.TryRead(out item)) + { + try + { + _action(item); + return s_trueTask; + } + catch (Exception exc) + { + return Task.FromException(exc); + } + } + return s_falseTask; + } + } + + /// Provides the concrete case used for channel reads with asynchronous processing. + private sealed class AsyncReadCase : Case + { + private readonly IReadableChannel _channel; + private readonly Func _action; + + internal AsyncReadCase(IReadableChannel channel, Func action) + { + _channel = channel; + _action = action; + } + + public override Task WaitAsync(CancellationToken cancellationToken) + { + return _channel.WaitToReadAsync(cancellationToken); + } + + public override Task TryInvokeAsync() + { + T item; + if (_channel.TryRead(out item)) + { + try + { + return _action(item).ContinueWith(t => + { + t.GetAwaiter().GetResult(); + return true; + }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + catch (Exception exc) + { + return Task.FromException(exc); + } + } + return s_falseTask; + } + } + + /// Provides the concrete case used for channel writes with synchronous processing. + private sealed class SyncWriteCase : Case + { + private readonly IWriteableChannel _channel; + private readonly T _item; + private readonly Action _action; + + internal SyncWriteCase(IWriteableChannel channel, T item, Action action) + { + _channel = channel; + _item = item; + _action = action; + } + + public override Task WaitAsync(CancellationToken cancellationToken) + { + return _channel.WaitToWriteAsync(cancellationToken); + } + + public override Task TryInvokeAsync() + { + if (_channel.TryWrite(_item)) + { + try + { + _action(); + return s_trueTask; + } + catch (Exception exc) + { + return Task.FromException(exc); + } + } + return s_falseTask; + } + } + + /// Provides the concrete case used for channel writes with asynchronous processing. + private sealed class AsyncWriteCase : Case + { + private readonly IWriteableChannel _channel; + private readonly T _item; + private readonly Func _action; + + internal AsyncWriteCase(IWriteableChannel channel, T item, Func action) + { + _channel = channel; + _item = item; + _action = action; + } + + public override Task WaitAsync(CancellationToken cancellationToken) + { + return _channel.WaitToWriteAsync(cancellationToken); + } + + public override Task TryInvokeAsync() + { + if (_channel.TryWrite(_item)) + { + try + { + return _action().ContinueWith(t => + { + t.GetAwaiter().GetResult(); + return true; + }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + catch (Exception exc) + { + return Task.FromException(exc); + } + } + return s_falseTask; + } + } + + /// Provides the concrete case used for synchronous default cases. + private sealed class SyncDefaultCase : Case + { + private readonly Action _action; + + internal SyncDefaultCase(Action action) + { + _action = action; + } + + public override Task TryInvokeAsync() + { + try + { + _action(); + return s_trueTask; + } + catch (Exception exc) + { + return Task.FromException(exc); + } + } + } + + /// Provides the concrete case used for asynchronous default cases. + private sealed class AsyncDefaultCase : Case + { + private readonly Func _action; + + internal AsyncDefaultCase(Func action) + { + _action = action; + } + + public override Task TryInvokeAsync() + { + try + { + return _action().ContinueWith(t => + { + t.GetAwaiter().GetResult(); + return true; + }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + catch (Exception exc) + { + return Task.FromException(exc); + } + } + } + + } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.EnumerableChannel.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.EnumerableChannel.cs new file mode 100644 index 00000000000..53be3222de0 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.EnumerableChannel.cs @@ -0,0 +1,108 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + /// Provides a channel around an enumerable. + private sealed class EnumerableChannel : IReadableChannel + { + /// A task completed when the channel is done. + private readonly TaskCompletionSource _completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + /// The wrapped enumerator. + private readonly IEnumerator _source; + /// true if the next item to yield is _source.Current; false if _source.MoveNext() needs to be invoked. + private bool _currentIsNext; + + /// The object used to synchronize access to state in the channel. + private object SyncObj { get { return _completion; } } + + /// Initializes the channel, getting an enumerator from the enumerable. + internal EnumerableChannel(IEnumerable source) + { + _source = source.GetEnumerator(); + } + + public Task Completion { get { return _completion.Task; } } + + public ValueTask ReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + // Fast-path cancellation check + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + // If we're not yet completed, ensure that _source.Current is the next item + // to yield, and yield it. + if (!_completion.Task.IsCompleted && (_currentIsNext || MoveNext())) + { + _currentIsNext = false; + return _source.Current; + } + } + + // No more data is available. Fail. + return Task.FromException(CreateInvalidCompletionException()); + } + + public bool TryRead(out T item) + { + lock (SyncObj) + { + // If we're not yet completed, ensure that _source.Current is the next item + // to yield, and yield it. + if (!_completion.Task.IsCompleted && (_currentIsNext || MoveNext())) + { + _currentIsNext = false; + item = _source.Current; + return true; + } + } + + // No more data is available. + item = default(T); + return false; + } + + public Task WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + // Fast-path cancellation check + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + // If we've already pushed the enumerator ahead in a previous call and _source.Current + // is the next value to yield, data is available. + if (_currentIsNext) + return s_trueTask; + + // If we're already completed, no more data is available. + if (_completion.Task.IsCompleted) + return s_falseTask; + + // Otherwise, move next. + return MoveNext() ? s_trueTask : s_falseTask; + } + } + + private bool MoveNext() + { + // Move next on the enumerator. + if (_source.MoveNext()) + { + _currentIsNext = true; + return true; + } + + // There's no more data available. Complete the channel. + _completion.TrySetResult(default(VoidResult)); + return false; + } + } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.Observable.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.Observable.cs new file mode 100644 index 00000000000..246a78c70b7 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.Observable.cs @@ -0,0 +1,142 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + /// Provides an observer for a writeable channel. + private sealed class ChannelObserver : IObserver + { + private readonly IWriteableChannel _channel; + + internal ChannelObserver(IWriteableChannel channel) + { + Debug.Assert(channel != null); + _channel = channel; + } + + public void OnCompleted() + { + _channel.Complete(); + } + + public void OnError(Exception error) + { + _channel.Complete(error); + } + + public void OnNext(T value) + { + _channel.WriteAsync(value).GetAwaiter().GetResult(); + } + } + + /// Provides an observable for a readable channel. + private sealed class ChannelObservable : IObservable + { + private readonly List> _observers = new List>(); + private readonly IReadableChannel _channel; + private CancellationTokenSource _cancelCurrentOperation; + + internal ChannelObservable(IReadableChannel channel) + { + Debug.Assert(channel != null); + _channel = channel; + } + + public IDisposable Subscribe(IObserver observer) + { + if (observer == null) + throw new ArgumentNullException("observer"); + + lock (_observers) + { + _observers.Add(observer); + if (_cancelCurrentOperation == null) + { + _cancelCurrentOperation = new CancellationTokenSource(); + CancellationToken token = _cancelCurrentOperation.Token; + Task.Run(() => ForwardAsync(token)); + } + return new Unsubscribe(this, observer); + } + } + + private async void ForwardAsync(CancellationToken cancellationToken) + { + Exception error = null; + try + { + IAsyncEnumerator e = _channel.GetAsyncEnumerator(cancellationToken); + while (await e.MoveNextAsync().ConfigureAwait(false)) + { + lock (_observers) + { + for (int i = 0; i < _observers.Count; i++) + { + _observers[i].OnNext(e.Current); + } + } + } + await _channel.Completion.ConfigureAwait(false); + } + catch (Exception exc) + { + error = exc; + } + finally + { + lock (_observers) + { + for (int i = 0; i < _observers.Count; i++) + { + if (error != null) + _observers[i].OnError(error); + else + _observers[i].OnCompleted(); + } + } + } + } + + private sealed class Unsubscribe : IDisposable + { + private readonly ChannelObservable _observable; + private IObserver _observer; + + internal Unsubscribe(ChannelObservable observable, IObserver observer) + { + Debug.Assert(observable != null); + Debug.Assert(observer != null); + + _observable = observable; + _observer = observer; + } + + public void Dispose() + { + lock (_observable._observers) + { + if (_observer != null) + { + bool removed = _observable._observers.Remove(_observer); + Debug.Assert(removed); + _observer = null; + + if (_observable._observers.Count == 0) + { + _observable._cancelCurrentOperation.Cancel(); + _observable._cancelCurrentOperation = null; + } + } + } + } + } + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.ReaderWriter.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.ReaderWriter.cs new file mode 100644 index 00000000000..e226e8eeb53 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.ReaderWriter.cs @@ -0,0 +1,94 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + private abstract class Interactor : TaskCompletionSource + { + protected Interactor() : base(TaskCreationOptions.RunContinuationsAsynchronously) { } + + internal bool Success(T item) + { + bool transitionedToCompleted = TrySetResult(item); + if (transitionedToCompleted) + { + Dispose(); + } + return transitionedToCompleted; + } + + internal bool Fail(Exception exception) + { + bool transitionedToCompleted = TrySetException(exception); + if (transitionedToCompleted) + { + Dispose(); + } + return transitionedToCompleted; + } + + protected virtual void Dispose() { } + } + + private class Reader : Interactor + { + internal static Reader Create(CancellationToken cancellationToken) + { + return cancellationToken.CanBeCanceled ? + new CancelableReader(cancellationToken) : + new Reader(); + } + } + + private class Writer : Interactor + { + internal T Item { get; private set; } + + internal static Writer Create(CancellationToken cancellationToken, T item) + { + Writer w = cancellationToken.CanBeCanceled ? + new CancelableWriter(cancellationToken) : + new Writer(); + w.Item = item; + return w; + } + } + + private sealed class CancelableReader : Reader + { + private CancellationToken _token; + private CancellationTokenRegistration _registration; + + internal CancelableReader(CancellationToken cancellationToken) + { + _token = cancellationToken; + _registration = cancellationToken.Register(s => { + var thisRef = (CancelableReader)s; + thisRef.TrySetCanceled(thisRef._token); + }, this); + } + + protected override void Dispose() { _registration.Dispose(); } + } + + private sealed class CancelableWriter : Writer + { + private CancellationToken _token; + private CancellationTokenRegistration _registration; + + internal CancelableWriter(CancellationToken cancellationToken) + { + _token = cancellationToken; + _registration = cancellationToken.Register(s => { + var thisRef = (CancelableWriter)s; + thisRef.TrySetCanceled(thisRef._token); + }, this); + } + + protected override void Dispose() { _registration.Dispose(); } + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SerializationChannel.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SerializationChannel.cs new file mode 100644 index 00000000000..790518e4987 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SerializationChannel.cs @@ -0,0 +1,268 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.IO; + +namespace System.Threading.Tasks.Channels +{ + // TODO: This implementation currently uses BinaryReader and BinaryWriter for serialization. + // This is fundamentally limited in several ways, and serves purely as an example for + // how channels can be implemented on top of Streams and a serialization mechanism. + // It's also limited in that these types don't currently provide async versions of the read + // and write methods. + + public static partial class Channel + { + /// Provides a channel for serializing data to a stream. + private sealed class SerializationChannel : IWriteableChannel + { + /// The writer to which data is written. + private readonly BinaryWriter _destination; + /// Whether has been called. + private bool _isCompleted; + + /// Initialize the channel. + /// The destination stream. + internal SerializationChannel(Stream destination) + { + _destination = new BinaryWriter(destination); + } + + public void Complete(Exception error = null) + { + // Complete the channel by disposing of the stream. + lock (_destination) + { + _isCompleted = true; + _destination.Dispose(); + } + } + + public unsafe bool TryWrite(T item) + { + // Write it out + lock (_destination) + { + if (_isCompleted) + { + return false; + } + + if (typeof(T) == typeof(bool)) _destination.Write((bool)(object)item); + else if (typeof(T) == typeof(byte)) _destination.Write((byte)(object)item); + else if (typeof(T) == typeof(char)) _destination.Write((char)(object)item); + else if (typeof(T) == typeof(decimal)) _destination.Write((decimal)(object)item); + else if (typeof(T) == typeof(double)) _destination.Write((double)(object)item); + else if (typeof(T) == typeof(float)) _destination.Write((float)(object)item); + else if (typeof(T) == typeof(int)) _destination.Write((int)(object)item); + else if (typeof(T) == typeof(long)) _destination.Write((long)(object)item); + else if (typeof(T) == typeof(sbyte)) _destination.Write((sbyte)(object)item); + else if (typeof(T) == typeof(short)) _destination.Write((short)(object)item); + else if (typeof(T) == typeof(string)) _destination.Write((string)(object)item); + else if (typeof(T) == typeof(uint)) _destination.Write((uint)(object)item); + else if (typeof(T) == typeof(ulong)) _destination.Write((ulong)(object)item); + else if (typeof(T) == typeof(ushort)) _destination.Write((ushort)(object)item); + else throw new InvalidOperationException(SR.InvalidOperationException_TypeNotSerializable); + } + + // And always return true. We have no mechanism on Stream for attempting to write + // without actually doing it. + return true; + } + + public Task WaitToWriteAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + // We assume we can always write to the stream (unless we're already canceled). + return + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : + _isCompleted ? s_falseTask : + s_trueTask; + } + + public Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken)) + { + // Fast-path cancellation check + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + return Task.Run(() => + { + bool result = TryWrite(item); + if (!result) + { + throw CreateInvalidCompletionException(); + } + return result; + }); + } + } + + /// Provides a channel for deserializing data from a stream. + private sealed class DeserializationChannel : IReadableChannel + { + /// The stream from which to read data. + private readonly BinaryReader _source; + /// A task that completes when the stream is at its end. + private readonly TaskCompletionSource _completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + /// Semaphore used to protect access to the underlying stream. + private readonly SemaphoreSlim _asyncGate = new SemaphoreSlim(1, 1); + /// A task for the next asynchronous read operation. + private Task> _nextAvailable; + + /// The object to use to synchronize all state on this channel. + private object SyncObj { get { return _source; } } + + internal DeserializationChannel(Stream source) + { + _source = new BinaryReader(source); + } + + public Task Completion { get { return _completion.Task; } } + + public ValueTask ReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + // Fast-path cancellation check + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + // If there's a task available, grab it, otherwise create one. + Task> next; + if (_nextAvailable != null) + { + next = _nextAvailable; + _nextAvailable = null; + } + else + { + next = ReadNextAsync(); + } + + // If the task has completed, return its results synchronously + if (next.IsCompleted) + { + if (next.Status == TaskStatus.RanToCompletion) + { + if (next.Result.Key) + { + return next.Result.Value; + } + else + { + return Task.FromException(CreateInvalidCompletionException()); + } + } + else + { + return PropagateErrorAsync(next); + } + } + + // Otherwise, wait for it asynchronously + return next.ContinueWith(t => + { + KeyValuePair result = t.GetAwaiter().GetResult(); + if (!result.Key) + throw CreateInvalidCompletionException(); + return result.Value; + }, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + } + + public unsafe bool TryRead(out T item) + { + lock (SyncObj) + { + // If there's a task available, it completed successfully, and it came back with results, + // grab it and return those results. + Task> next = _nextAvailable; + if (next != null && next.Status == TaskStatus.RanToCompletion && next.Result.Key) + { + _nextAvailable = null; + item = next.Result.Value; + return true; + } + } + + item = default(T); + return false; + } + + public Task WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + // If there's no current task, create one. + Task> next = _nextAvailable; + if (next == null) + { + _nextAvailable = next = ReadNextAsync(); + } + + // If the task is completed, we can return synchronously. + if (next.IsCompleted) + { + return next.Status == TaskStatus.RanToCompletion ? + (next.Result.Key ? s_trueTask : s_falseTask) : + PropagateErrorAsync(next); + } + + // Otherwise, return asynchronously. + return next.ContinueWith(t => t.GetAwaiter().GetResult().Key, + cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default); + } + } + + /// Reads the next T item from the stream asynchronously. + /// A task whose result is a key/value pair indicating whether there was more data or not and the T item result. + private Task> ReadNextAsync() + { + // Ensure there's only one read on the underlying stream at a time, and that if we need to make + // multiple reads to get enough data for the T instance that we can do them without intervening reads + // from other calls. + return _asyncGate.WaitAsync().ContinueWith((t, s) => + { + var thisRef = (DeserializationChannel)s; + var reader = thisRef._source; + try + { + T item; + if (typeof(T) == typeof(bool)) item = (T)(object)reader.ReadBoolean(); + else if (typeof(T) == typeof(byte)) item = (T)(object)reader.ReadByte(); + else if (typeof(T) == typeof(char)) item = (T)(object)reader.ReadChar(); + else if (typeof(T) == typeof(decimal)) item = (T)(object)reader.ReadDecimal(); + else if (typeof(T) == typeof(double)) item = (T)(object)reader.ReadDouble(); + else if (typeof(T) == typeof(float)) item = (T)(object)reader.ReadSingle(); + else if (typeof(T) == typeof(int)) item = (T)(object)reader.ReadInt32(); + else if (typeof(T) == typeof(long)) item = (T)(object)reader.ReadInt64(); + else if (typeof(T) == typeof(sbyte)) item = (T)(object)reader.ReadSByte(); + else if (typeof(T) == typeof(short)) item = (T)(object)reader.ReadInt16(); + else if (typeof(T) == typeof(string)) item = (T)(object)reader.ReadString(); + else if (typeof(T) == typeof(uint)) item = (T)(object)reader.ReadUInt32(); + else if (typeof(T) == typeof(ulong)) item = (T)(object)reader.ReadUInt64(); + else if (typeof(T) == typeof(ushort)) item = (T)(object)reader.ReadUInt16(); + else throw new InvalidOperationException(SR.InvalidOperationException_TypeNotSerializable); + return new KeyValuePair(true, item); + } + catch (EndOfStreamException) + { + CompleteWithOptionalError(_completion, null); + return new KeyValuePair(false, default(T)); + } + finally + { + thisRef._asyncGate.Release(); + } + }, this, CancellationToken.None, TaskContinuationOptions.None, TaskScheduler.Default); + } + + } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SimpleQueue.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SimpleQueue.cs new file mode 100644 index 00000000000..724aeebd62e --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SimpleQueue.cs @@ -0,0 +1,90 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + [DebuggerDisplay("Count = {_size}")] + private sealed class SimpleQueue + { + private T[] _array = Array.Empty(); + private int _head; // First valid element in the queue + private int _tail; // Last valid element in the queue + private int _size; // Number of elements. + + public int Count { get { return _size; } } + + public void Enqueue(T item) + { + if (_size == _array.Length) + { + Grow(); + } + + _array[_tail] = item; + if (++_tail == _array.Length) + { + _tail = 0; + } + _size++; + } + + public T Dequeue() + { + T item = _array[_head]; + _array[_head] = default(T); + + if (++_head == _array.Length) + { + _head = 0; + } + _size--; + + return item; + } + + public IEnumerator GetEnumerator() // meant for debug purposes only + { + int pos = _head; + int count = _size; + while (count-- > 0) + { + yield return _array[pos]; + pos = (pos + 1) % _size; + } + } + + private void Grow() + { + const int MinimumGrow = 4; + + int capacity = (int)(_array.Length * 2L); + if (capacity < _array.Length + MinimumGrow) + { + capacity = _array.Length + MinimumGrow; + } + + T[] newArray = new T[capacity]; + + if (_head < _tail) + { + Array.Copy(_array, _head, newArray, 0, _size); + } + else + { + Array.Copy(_array, _head, newArray, 0, _array.Length - _head); + Array.Copy(_array, 0, newArray, _array.Length - _head, _tail); + } + + _array = newArray; + _head = 0; + _tail = (_size == capacity) ? 0 : _size; + } + + } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SpscUnboundedChannel.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SpscUnboundedChannel.cs new file mode 100644 index 00000000000..c1483f9a48a --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.SpscUnboundedChannel.cs @@ -0,0 +1,206 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + /// Provides a buffered channel of unbounded capacity. + [DebuggerDisplay("Items={ItemsCountForDebugger}")] + [DebuggerTypeProxy(typeof(DebugEnumeratorDebugView<>))] + private sealed class SpscUnboundedChannel : IChannel, IDebugEnumerable + { + /// Task that indicates the channel has completed. + private readonly TaskCompletionSource _completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + private readonly SingleProducerSingleConsumerQueue _items = new SingleProducerSingleConsumerQueue(); + + private volatile Exception _doneWriting; + private Reader _blockedReader; + private Reader _waitingReader; + + private object SyncObj { get { return _items; } } + + public Task Completion { get { return _completion.Task; } } + + public void Complete(Exception error = null) + { + lock (SyncObj) + { + if (_doneWriting != null) throw CreateInvalidCompletionException(); + _doneWriting = error ?? s_doneWritingSentinel; + + if (_items.IsEmpty) + { + CompleteWithOptionalError(_completion, error); + if (_blockedReader != null) + { + _blockedReader.Fail(error ?? CreateInvalidCompletionException()); + _blockedReader = null; + } + if (_waitingReader != null) + { + _waitingReader.Success(false); + _waitingReader = null; + } + } + } + } + + public ValueTask ReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + T item; + if (TryRead(out item)) + return item; + + return ReadAsyncCore(cancellationToken); + } + + private ValueTask ReadAsyncCore(CancellationToken cancellationToken = default(CancellationToken)) + { + lock (SyncObj) + { + T item; + + if (TryReadCore(out item)) + return item; + + if (_doneWriting != null) + return Task.FromException(_doneWriting != s_doneWritingSentinel ? _doneWriting : CreateInvalidCompletionException()); + + Reader reader = Reader.Create(cancellationToken); + _blockedReader = reader; + return reader.Task; + } + } + + public bool TryRead(out T item) + { + SpinWait spinner = default(SpinWait); + do + { + if (TryReadCore(out item)) + { + return true; + } + spinner.SpinOnce(); + } + while (!spinner.NextSpinWillYield && _doneWriting == null); + return false; + } + + private bool TryReadCore(out T item) + { + if (_items.TryDequeue(out item)) + { + if (_doneWriting != null && _items.IsEmpty) + { + CompleteWithOptionalError(_completion, _doneWriting); + } + return true; + } + return false; + } + + public bool TryWrite(T item) + { + lock (SyncObj) + { + var b = _blockedReader; + if (b != null) + { + _blockedReader = null; + if (b.Success(item)) + { + return true; + } + } + + if (_waitingReader != null) + { + _waitingReader.Success(true); + _waitingReader = null; + } + + if (_doneWriting != null) + { + return false; + } + + _items.Enqueue(item); + return true; + } + } + + public Task WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + var spinner = default(SpinWait); + do + { + if (!_items.IsEmpty) + { + return s_trueTask; + } + spinner.SpinOnce(); + } + while (!spinner.NextSpinWillYield); + + lock (SyncObj) + { + if (!_items.IsEmpty) + return s_trueTask; + + if (_waitingReader != null) + { + _waitingReader.Fail(CreateSingleReaderWriterMisuseException()); + _waitingReader = null; + return Task.FromException(CreateSingleReaderWriterMisuseException()); + } + + if (_doneWriting != null) + return s_falseTask; + + Reader reader = Reader.Create(cancellationToken); + _waitingReader = reader; + return reader.Task; + } + } + + public Task WaitToWriteAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + // Other than for cancellation, writing can always be done if we haven't completed, as we're unbounded. + lock (SyncObj) + { + return _doneWriting == null ? s_trueTask : s_falseTask; + } + } + + public Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken)) + { + // Writing always succeeds (unless we've already completed writing or cancellation has been requested), + // so just TryWrite and return a completed task. + return + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : + TryWrite(item) ? Task.CompletedTask : + Task.FromException(CreateInvalidCompletionException()); + } + + /// Gets the number of items in the channel. This should only be used by the debugger. + private int ItemsCountForDebugger { get { return _items.Count; } } + + /// Gets an enumerator the debugger can use to show the contents of the channel. + IEnumerator IDebugEnumerable.GetEnumerator() + { + return _items.GetEnumerator(); + } + } + } +} \ No newline at end of file diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.UnboundedChannel.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.UnboundedChannel.cs new file mode 100644 index 00000000000..baae2095025 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.UnboundedChannel.cs @@ -0,0 +1,244 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + /// Provides a buffered channel of unbounded capacity. + [DebuggerDisplay("Items={ItemsCountForDebugger}")] + [DebuggerTypeProxy(typeof(DebugEnumeratorDebugView<>))] + private sealed class UnboundedChannel : IChannel, IDebugEnumerable + { + /// Task that indicates the channel has completed. + private readonly TaskCompletionSource _completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + /// The items in the channel. + private readonly SimpleQueue _items = new SimpleQueue(); + /// Readers blocked reading from the channel. + private readonly SimpleQueue> _blockedReaders = new SimpleQueue>(); + /// Readers waiting for a notification that data is available. + private readonly SimpleQueue> _waitingReaders = new SimpleQueue>(); + /// Set to non-null once Complete has been called. + private Exception _doneWriting; + + /// Gets the object used to synchronize access to all state on this instance. + private object SyncObj { get { return _items; } } + + public Task Completion { get { return _completion.Task; } } + + [Conditional("DEBUG")] + private void AssertInvariants() + { + Debug.Assert(SyncObj != null, "The sync obj must not be null."); + Debug.Assert(Monitor.IsEntered(SyncObj), "Invariants can only be validated while holding the lock."); + + if (_items.Count > 0) + { + Debug.Assert(_blockedReaders.Count == 0, "There's data available, so there shouldn't be any blocked readers."); + Debug.Assert(_waitingReaders.Count == 0, "There's data available, so there shouldn't be any waiting readers."); + Debug.Assert(!_completion.Task.IsCompleted, "We still have data available, so shouldn't be completed."); + } + if (_blockedReaders.Count > 0 || _waitingReaders.Count > 0) + { + Debug.Assert(_items.Count == 0, "There are blocked/waiting readers, so there shouldn't be any data available."); + } + if (_completion.Task.IsCompleted) + { + Debug.Assert(_doneWriting != null, "We're completed, so we must be done writing."); + } + } + + public void Complete(Exception error = null) + { + lock (SyncObj) + { + AssertInvariants(); + + // Mark that we're done writing + if (_doneWriting != null) throw CreateInvalidCompletionException(); + _doneWriting = error ?? s_doneWritingSentinel; + + // If there are no items in the queue, then we're done (there won't be any more coming). + if (_items.Count == 0) + { + CompleteWithOptionalError(_completion, error); + } + + // If there are any blocked readers, then since we won't be getting any more + // data and there can't be any currently in the queue (or else they wouldn't + // be blocked), fail them all. + while (_blockedReaders.Count > 0) + { + var reader = _blockedReaders.Dequeue(); + reader.Fail(error ?? CreateInvalidCompletionException()); + } + + // Similarly, if there are any waiting readers, let them know + // no more data will be coming. + WakeUpWaiters(_waitingReaders, false); + } + } + + public ValueTask ReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + T item; + if (TryRead(out item)) + return item; + + return ReadAsyncCore(cancellationToken); + } + + public ValueTask ReadAsyncCore(CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + AssertInvariants(); + + // If there are any items, return one. + if (_items.Count > 0) + { + // Dequeue an item + T item = _items.Dequeue(); + if (_doneWriting != null && _items.Count == 0) + { + // If we've now emptied the items queue and we're not getting any more, complete. + CompleteWithOptionalError(_completion, _doneWriting); + } + + return item; + } + + // There are no items, so if we're done writing, fail. + if (_doneWriting != null) + return Task.FromException(_doneWriting != s_doneWritingSentinel ? _doneWriting : CreateInvalidCompletionException()); + + // Otherwise, queue the reader. + var reader = Reader.Create(cancellationToken); + _blockedReaders.Enqueue(reader); + return reader.Task; + } + } + + public Task WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + lock (SyncObj) + { + AssertInvariants(); + + // If there are any items, readers can try to get them. + if (_items.Count > 0) + return s_trueTask; + + // There are no items, so if we're done writing, there's never going to be data available. + if (_doneWriting != null) + return s_falseTask; + + // Queue the waiter + var r = Reader.Create(cancellationToken); + _waitingReaders.Enqueue(r); + return r.Task; + } + } + + public bool TryRead(out T item) + { + SpinWait spinner = default(SpinWait); + do + { + lock (SyncObj) + { + AssertInvariants(); + + // Dequeue an item if we can + if (_items.Count > 0) + { + item = _items.Dequeue(); + if (_doneWriting != null && _items.Count == 0) + { + // If we've now emptied the items queue and we're not getting any more, complete. + CompleteWithOptionalError(_completion, _doneWriting); + } + return true; + } + } + spinner.SpinOnce(); + } + while (!spinner.NextSpinWillYield); + + item = default(T); + return false; + } + + public bool TryWrite(T item) + { + lock (SyncObj) + { + AssertInvariants(); + + // If writing has already been marked as done, fail the write. + if (_doneWriting != null) + return false; + + // If there are any blocked readers, wake one of them up to transfer + // the data to. Note that readers may be canceled but still be in the + // queue, so we need to loop. + while (_blockedReaders.Count > 0) + { + Reader r = _blockedReaders.Dequeue(); + if (r.Success(item)) + { + return true; + } + } + + // There were no blocked readers, so just add the data to the queue + _items.Enqueue(item); + + // Then let any waiting readers know that they should try to read it. + WakeUpWaiters(_waitingReaders, true); + + return true; + } + } + + public Task WaitToWriteAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + { + return Task.FromCanceled(cancellationToken); + } + + // Other than for cancellation, writing can always be done if we haven't completed, as we're unbounded. + lock (SyncObj) + { + return _doneWriting == null ? s_trueTask : s_falseTask; + } + } + + public Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken)) + { + // Writing always succeeds (unless we've already completed writing or cancellation has been requested), + // so just TryWrite and return a completed task. + return + cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) : + TryWrite(item) ? Task.CompletedTask : + Task.FromException(CreateInvalidCompletionException()); + } + + /// Gets the number of items in the channel. This should only be used by the debugger. + private int ItemsCountForDebugger { get { return _items.Count; } } + + /// Gets an enumerator the debugger can use to show the contents of the channel. + IEnumerator IDebugEnumerable.GetEnumerator() + { + return _items.GetEnumerator(); + } + } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.UnbufferedChannel.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.UnbufferedChannel.cs new file mode 100644 index 00000000000..541dae8f34e --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.UnbufferedChannel.cs @@ -0,0 +1,288 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Threading.Tasks.Channels +{ + public static partial class Channel + { + /// Provides an unbuffered channel, such that a reader and a writer must rendezvous to succeed. + [DebuggerDisplay("Waiting Writers = {WaitingWritersCountForDebugger}, Waiting Readers = {WaitingReadersCountForDebugger}")] + [DebuggerTypeProxy(typeof(UnbufferedChannel<>.DebugView))] + private sealed class UnbufferedChannel : IChannel + { + /// Task that represents the completion of the channel. + private readonly TaskCompletionSource _completion = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); + /// A queue of readers blocked waiting to be matched with a writer. + private readonly SimpleQueue> _blockedReaders = new SimpleQueue>(); + /// A queue of writers blocked waiting to be matched with a reader. + private readonly SimpleQueue> _blockedWriters = new SimpleQueue>(); + /// Tasks waiting for data to be available to read. + private readonly SimpleQueue> _waitingReaders = new SimpleQueue>(); + /// Tasks waiting for data to be available to write. + private readonly SimpleQueue> _waitingWriters = new SimpleQueue>(); + + /// Gets an object used to synchronize all state on the instance. + private object SyncObj { get { return _completion; } } + + public Task Completion { get { return _completion.Task; } } + + [Conditional("DEBUG")] + private void AssertInvariants() + { + Debug.Assert(SyncObj != null, "The sync obj must not be null."); + Debug.Assert(Monitor.IsEntered(SyncObj), "Invariants can only be validated while holding the lock."); + + if (_blockedReaders.Count > 0) + { + Debug.Assert(_blockedWriters.Count == 0, "If there are blocked readers, there can't be blocked writers."); + } + if (_blockedWriters.Count > 0) + { + Debug.Assert(_blockedReaders.Count == 0, "If there are blocked writers, there can't be blocked readers."); + } + if (_completion.Task.IsCompleted) + { + Debug.Assert(_blockedReaders.Count == 0, "No readers can be blocked after we've completed."); + Debug.Assert(_blockedWriters.Count == 0, "No writers can be blocked after we've completed."); + } + } + + public void Complete(Exception error = null) + { + lock (SyncObj) + { + AssertInvariants(); + + // Mark the channel as being done. Since there's no buffered data, we can complete immediately. + if (_completion.Task.IsCompleted) throw CreateInvalidCompletionException(); + CompleteWithOptionalError(_completion, error); + + // Fail any blocked readers, as there will be no writers to pair them with. + while (_blockedReaders.Count > 0) + { + var reader = _blockedReaders.Dequeue(); + reader.Fail(error ?? CreateInvalidCompletionException()); + } + + // Fail any blocked writers, as there will be no readers to pair them with. + while (_blockedWriters.Count > 0) + { + var writer = _blockedWriters.Dequeue(); + writer.Fail(CreateInvalidCompletionException()); + } + + // Let any waiting readers and writers know there won't be any more data + WakeUpWaiters(_waitingReaders, false); + WakeUpWaiters(_waitingWriters, false); + } + } + + public ValueTask ReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + T item; + if (TryRead(out item)) + return item; + + return ReadAsyncCore(cancellationToken); + } + + private ValueTask ReadAsyncCore(CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + AssertInvariants(); + + // If we're already completed, nothing to read. + if (_completion.Task.IsCompleted) + { + return Task.FromException(_completion.Task.IsFaulted ? _completion.Task.Exception.InnerException : CreateInvalidCompletionException()); + } + + // If there are any blocked writers, find one to pair up with + // and get its data. Writers that got canceled will remain in the queue, + // so we need to loop to skip past them. + while (_blockedWriters.Count > 0) + { + Writer w = _blockedWriters.Dequeue(); + if (w.Success(default(VoidResult))) + { + return w.Item; + } + } + + // No writer found to pair with. Queue the reader. + var r = Reader.Create(cancellationToken); + _blockedReaders.Enqueue(r); + + // And let any waiting writers know it's their lucky day. + WakeUpWaiters(_waitingWriters, true); + + return r.Task; + } + } + + public bool TryRead(out T item) + { + lock (SyncObj) + { + AssertInvariants(); + + // Try to find a writer to pair with + while (_blockedWriters.Count > 0) + { + Writer w = _blockedWriters.Dequeue(); + if (w.Success(default(VoidResult))) + { + item = w.Item; + return true; + } + } + } + + // None found + item = default(T); + return false; + } + + public bool TryWrite(T item) + { + lock (SyncObj) + { + AssertInvariants(); + + // Try to find a reader to pair with + while (_blockedReaders.Count > 0) + { + Reader r = _blockedReaders.Dequeue(); + if (r.Success(item)) + { + return true; + } + } + } + + // None found + return false; + } + + public Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken)) + { + if (cancellationToken.IsCancellationRequested) + return Task.FromCanceled(cancellationToken); + + lock (SyncObj) + { + // Fail if we've already completed + if (_completion.Task.IsCompleted) + { + return Task.FromException(CreateInvalidCompletionException()); + } + + // Try to find a reader to pair with. Canceled readers remain in the queue, + // so we need to loop until we find one. + while (_blockedReaders.Count > 0) + { + Reader r = _blockedReaders.Dequeue(); + if (r.Success(item)) + { + return Task.CompletedTask; + } + } + + // No reader was available. Queue the writer. + var w = Writer.Create(cancellationToken, item); + _blockedWriters.Enqueue(w); + + // And let any waiting readers know it's their lucky day. + WakeUpWaiters(_waitingReaders, true); + + return w.Task; + } + } + + public Task WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + lock (SyncObj) + { + // If we're done writing, fail. + if (_completion.Task.IsCompleted) + { + return s_falseTask; + } + + // If there's a blocked writer, we can read. + if (_blockedWriters.Count > 0) + { + return s_trueTask; + } + + // Otherwise, queue the waiter. + var r = Reader.Create(cancellationToken); + _waitingReaders.Enqueue(r); + return r.Task; + } + } + + public Task WaitToWriteAsync(CancellationToken cancellationToken = default(CancellationToken)) + { + lock (SyncObj) + { + // If we're done writing, fail. + if (_completion.Task.IsCompleted) + { + return s_falseTask; + } + + // If there's a blocked reader, we can write + if (_blockedReaders.Count > 0) + { + return s_trueTask; + } + + // Otherwise, queue the writer + var w = Reader.Create(cancellationToken); + _waitingWriters.Enqueue(w); + return w.Task; + } + } + + /// Gets the number of writers waiting on the channel. This should only be used by the debugger. + private int WaitingWritersCountForDebugger { get { return _waitingWriters.Count; } } + + /// Gets the number of readers waiting on the channel. This should only be used by the debugger. + private int WaitingReadersCountForDebugger { get { return _waitingReaders.Count; } } + + private sealed class DebugView + { + private readonly UnbufferedChannel _channel; + + public DebugView(UnbufferedChannel channel) + { + _channel = channel; + } + + public int WaitingReaders { get { return _channel._waitingReaders.Count; } } + public int WaitingWriters { get { return _channel._waitingWriters.Count; } } + public int BlockedReaders { get { return _channel._blockedReaders.Count; } } + public T[] BlockedWriters + { + get + { + var items = new List(); + foreach (Writer blockedWriter in _channel._blockedWriters) + { + items.Add(blockedWriter.Item); + } + return items.ToArray(); + } + } + } + } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.cs new file mode 100644 index 00000000000..c4bda48ea1f --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/Channel.cs @@ -0,0 +1,268 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; +using System.IO; +using System.Runtime.CompilerServices; + +namespace System.Threading.Tasks.Channels +{ + /// Provides static methods for creating and working with channels. + public static partial class Channel + { + /// Sentinel object used to indicate being done writing. + private static readonly Exception s_doneWritingSentinel = new Exception("s_doneWritingSentinel"); + /// A cached task with a Boolean true result. + private static readonly Task s_trueTask = Task.FromResult(true); + /// A cached task with a Boolean false result. + private static readonly Task s_falseTask = Task.FromResult(false); + /// Sentinel value to indicate an infinite bound. + public const int Unbounded = -1; + + /// + /// Creates a buffered channel. If the specified is not , + /// the channel may only store up to that number of items; attempts to store more than that will result in writes + /// being delayed. + /// + /// Specifies the type of data stored in the channel. + /// The new channel. + public static IChannel Create(int bufferedCapacity = Unbounded, bool singleReaderWriter = false) + { + if (bufferedCapacity <= 0 && bufferedCapacity != Unbounded) + throw new ArgumentOutOfRangeException("bufferedCapacity"); + + return bufferedCapacity == Unbounded ? + singleReaderWriter ? + (IChannel)new SpscUnboundedChannel() : + new UnboundedChannel() : + new BoundedChannel(bufferedCapacity); + } + + /// + /// Creates an unbuffered channel. As the resulting channel is unbuffered, readers and writers will not complete + /// until a corresponding reader or writer is available. + /// + /// Specifies the type of data stored in the channel. + /// The new channel. + public static IChannel CreateUnbuffered() + { + return new UnbufferedChannel(); + } + + /// Creates a channel for reading instances from the source stream. + /// Specifies the type of data to be read. This must be an unmanaged/primitive type. + /// The source stream from which to read data. + /// A channel that reads elements from the source stream. + public static IReadableChannel ReadFromStream(Stream source) + { + if (source == null) + throw new ArgumentNullException("source"); + if (!source.CanRead) + throw new ArgumentException(SR.ArgumentException_SourceStreamNotReadable, "source"); + + return new DeserializationChannel(source); + } + + /// Creates a channel for writing instances to the destination stream. + /// Specifies the type of data to be written. This must be an unmanaged/primitive type. + /// The destination stream to which to write data. + /// A channel that write elements to the destination stream. + public static IWriteableChannel WriteToStream(Stream destination) + { + if (destination == null) + throw new ArgumentNullException("destination"); + if (!destination.CanWrite) + throw new ArgumentException(SR.ArgumentException_DestinationStreamNotWritable, "destination"); + + return new SerializationChannel(destination); + } + + /// Create a channel that consumes data from the source enumerable. + /// Specifies the type of data in the enumerable. + /// The source enumerable from which to read data. + /// A channel that reads data from the source enumerable. + public static IReadableChannel CreateFromEnumerable(IEnumerable source) + { + if (source == null) + throw new ArgumentNullException("source"); + + return new EnumerableChannel(source); + } + + /// Creates an observable for a channel. + /// Specifies the type of data in the channel. + /// The channel to be treated as an observable. + /// An observable that pulls data from the source. + public static IObservable AsObservable(this IReadableChannel source) + { + if (source == null) + throw new ArgumentNullException("source"); + + return (IObservable)s_channelToObservable.GetValue( + source, + s => new ChannelObservable((IReadableChannel)s)); + } + + /// Table mapping from a channel to the shared observable wrapping it. + private static ConditionalWeakTable s_channelToObservable = new ConditionalWeakTable(); + + /// Creates an observer for a writeable channel. + /// Specifies the type of data in the channel. + /// The channel to be treated as an observer. + /// An observer that forwards to the specified channel. + public static IObserver AsObserver(this IWriteableChannel target) + { + if (target == null) + throw new ArgumentNullException("target"); + return new ChannelObserver(target); + } + + /// Gets an awaiter that enables directly awaiting a channel to read data from it. + /// Specifies the type of data in the channel. + /// The channel to await and from which to read. + /// An awaiter for reading data from the channel. + /// + /// Getting the awaiter will initiate a read operation on the channel. + /// + public static ValueTask.ValueTaskAwaiter GetAwaiter(this IReadableChannel channel) + { + if (channel == null) + throw new ArgumentNullException("channel"); + + return new ValueTask.ValueTaskAwaiter(channel.ReadAsync(), continueOnCapturedContext: true); + } + + /// Gets an async enumerator of the data in the channel. + /// Specifies the type of data being enumerated. + /// The channel from which to read data. + /// The cancellation token to use to cancel the asynchronous enumeration. + /// The async enumerator. + public static IAsyncEnumerator GetAsyncEnumerator( + this IReadableChannel channel, CancellationToken cancellationToken = default(CancellationToken)) + { + if (channel == null) + throw new ArgumentNullException("channel"); + + return new AsyncEnumerator(channel, cancellationToken); + } + + /// Creates a case-select builder and adds a case for channel reading. + /// Specifies the type of data in the channel. + /// The channel from which to read. + /// The action to invoke with data read from the channel. + /// This builder. + public static CaseBuilder CaseRead(IReadableChannel channel, Action action) + { + return new CaseBuilder().CaseRead(channel, action); + } + + /// Creates a case-select builder and adds a case for channel reading. + /// Specifies the type of data in the channel. + /// The channel from which to read. + /// The asynchronous function to invoke with data read from the channel. + /// This builder. + public static CaseBuilder CaseRead(IReadableChannel channel, Func func) + { + return new CaseBuilder().CaseRead(channel, func); + } + + /// Creates a case-select builder and adds a case for channel writing. + /// Specifies the type of data in the channel. + /// The channel to which to write. + /// The data to write to the channel + /// The action to invoke after the data has been written. + /// This builder. + public static CaseBuilder CaseWrite(IWriteableChannel channel, T item, Action action) + { + return new CaseBuilder().CaseWrite(channel, item, action); + } + + /// Creates a case-select builder and adds a case for channel writing. + /// Specifies the type of data in the channel. + /// The channel to which to write. + /// The data to write to the channel + /// The asynchronous function to invoke after the data has been written. + /// This builder. + public static CaseBuilder CaseWrite(IWriteableChannel channel, T item, Func func) + { + return new CaseBuilder().CaseWrite(channel, item, func); + } + + /// Completes the specified TaskCompletionSource. + /// The source to complete. + /// + /// The optional exception with which to complete. + /// If this is null, the source will be completed successfully. + /// If this is an OperationCanceledException, it'll be completed with the exception's token. + /// Otherwise, it'll be completed as faulted with the exception. + /// + private static void CompleteWithOptionalError(TaskCompletionSource tcs, Exception error) + { + OperationCanceledException oce = error as OperationCanceledException; + if (oce != null) + tcs.TrySetCanceled(oce.CancellationToken); + else if (error != null && error != s_doneWritingSentinel) + tcs.TrySetException(error); + else + tcs.TrySetResult(default(VoidResult)); + } + + /// + /// Given an already faulted or canceled Task, returns a new generic task + /// with the same failure or cancellation token. + /// + private static async Task PropagateErrorAsync(Task t) + { + Debug.Assert(t.IsFaulted || t.IsCanceled); + await t; + throw new InvalidOperationException(); // Awaiting should have thrown + } + + /// Removes all waiters from the queue, completing each. + /// The queue of waiters to complete. + /// The value with which to complete each waiter. + private static void WakeUpWaiters(SimpleQueue> waiters, bool result) + { + if (waiters.Count > 0) + WakeUpWaitersCore(waiters, result); // separated out to streamline inlining + } + + /// Core of WakeUpWaiters, separated out for performance due to inlining. + private static void WakeUpWaitersCore(SimpleQueue> waiters, bool result) + { + while (waiters.Count > 0) + { + waiters.Dequeue().Success(result); + } + } + + /// Creates an exception detailing concurrent use of a single reader/writer channel. + private static Exception CreateSingleReaderWriterMisuseException() + { + return new InvalidOperationException(SR.InvalidOperationException_SingleReaderWriterUsedConcurrently).InitializeStackTrace(); + } + + /// Creates and returns an exception object to indicate that a channel has been closed. + private static Exception CreateInvalidCompletionException() + { + return new ClosedChannelException().InitializeStackTrace(); + } + + /// Exception thrown when a channel is used incorrectly after it's been closed. + private sealed class ClosedChannelException : InvalidOperationException + { + public ClosedChannelException() : base(SR.ClosedChannelException_DefaultMessage) { } + } + + /// Initializes the stack trace of an Exception by throwing and catching it. + /// The exception to initialize. + /// The same exception. + private static Exception InitializeStackTrace(this Exception exc) + { + try { throw exc; } + catch { return exc; } + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/IChannel.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/IChannel.cs new file mode 100644 index 00000000000..f4d65ed0c45 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/IChannel.cs @@ -0,0 +1,66 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Threading.Tasks.Channels +{ + /// Represents a channel to and from which data can be written and read. + /// Specifies the type of data items readable from and writable to the channel. + public interface IChannel : IChannel + { + } + + /// Represents a channel to and from which data can be written and read. + /// Specifies the type of data items writable to the channel. + /// Specifies the type of data items readable from the channel. + public interface IChannel : IWriteableChannel, IReadableChannel + { + } + + /// Represents a channel from which data can be read. + /// Specifies the type of data items readable from the channel. + public interface IReadableChannel + { + /// Asynchronously reads a data item from the channel. + /// The cancellation token to use to cancel the operation. + /// A task representing the asynchronous read operation. + ValueTask ReadAsync(CancellationToken cancellationToken = default(CancellationToken)); + + /// Asynchronously waits for a data to be available. + /// The cancellation token to use to cancel the operation. + /// A task representing the asynchronous wait. + Task WaitToReadAsync(CancellationToken cancellationToken = default(CancellationToken)); + + /// Attempt to read an item from the channel. + /// If the read succeeds, the resulting read item. + /// true if an item was read; false if no item could be read. + bool TryRead(out T item); + + /// Gets a task that completes when the channel is completed and has no more data to be read. + Task Completion { get; } + } + + /// Represents a channel to which data can be written. + /// Specifies the type of data items writable to the channel. + public interface IWriteableChannel + { + /// Asynchronously writes a data item to the channel. + /// The item to write. + /// The cancellation token to use to cancel the operation. + /// A task representing the asynchronous write operation. + Task WriteAsync(T item, CancellationToken cancellationToken = default(CancellationToken)); + + /// Asynchronously waits for space to be available. + /// The cancellation token to use to cancel the operation. + /// A task representing the asynchronous wait. + Task WaitToWriteAsync(CancellationToken cancellationToken = default(CancellationToken)); + + /// Attempt to write an item to the channel. + /// The item to write. + /// true if the item was written to the channel; otherwise, false. + bool TryWrite(T item); + + /// Marks the channel is being complete, meaning no more items will be written to it. + /// Optional Exception indicating a failure that's causing the channel to complete. + void Complete(Exception error = null); + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/IDebugEnumerator.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/IDebugEnumerator.cs new file mode 100644 index 00000000000..a2b7a5b6426 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/Channels/IDebugEnumerator.cs @@ -0,0 +1,29 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Diagnostics; + +namespace System.Threading.Tasks.Channels +{ + interface IDebugEnumerable + { + IEnumerator GetEnumerator(); + } + + internal sealed class DebugEnumeratorDebugView + { + public DebugEnumeratorDebugView(IDebugEnumerable enumerable) + { + var list = new List(); + foreach (T item in enumerable) + { + list.Add(item); + } + Items = list.ToArray(); + } + + [DebuggerBrowsable(DebuggerBrowsableState.RootHidden)] + public T[] Items { get; private set; } + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/ValueTask.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/ValueTask.cs new file mode 100644 index 00000000000..c359e396ed2 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/ValueTask.cs @@ -0,0 +1,152 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Runtime.CompilerServices; + +namespace System.Threading.Tasks +{ + /// Value type discriminated union for a TResult and a . + /// The type of the result. + [DebuggerDisplay("Status = {DebuggerStatus}, Result = {DebuggerResult}")] + public struct ValueTask + { + /// The task. this will be non-null iff the operation didn't complete successfully synchronously. + private readonly Task _task; + /// The result to be used if the operation completed successfully synchronously. + private readonly TResult _result; + + /// Initialize the TaskValue with the result of the successful operation. + /// The result. + public ValueTask(TResult result) + { + _result = result; + _task = null; + } + + /// + /// Initialize the TaskValue with a that represents + /// the non-successful or incomplete operation. + /// + /// + public ValueTask(Task task) + { + Debug.Assert(task != null); + _result = default(TResult); + _task = task; + } + + /// Implicit operator to wrap a TaskValue around a task. + public static implicit operator ValueTask(Task task) + { + return new ValueTask(task); + } + + /// Implicit operator to wrap a TaskValue around a result. + public static implicit operator ValueTask(TResult result) + { + return new ValueTask(result); + } + + /// + /// Gets a object to represent this TaskValue. It will + /// either return the wrapped task object if one exists, or it'll manufacture a new + /// task object to represent the result. + /// + public Task AsTask() + { + return _task ?? Task.FromResult(_result); + } + + /// Gets whether the TaskValue represents a successfully completed operation. + public bool IsRanToCompletion + { + get { return _task == null || _task.Status == TaskStatus.RanToCompletion; } + } + + /// Gets the result. + public TResult Result + { + get { return _task == null ? _result : _task.GetAwaiter().GetResult(); } + } + + /// Gets an awaiter for this value. + public ValueTaskAwaiter GetAwaiter() + { + return new ValueTaskAwaiter(this, continueOnCapturedContext: true); + } + + /// Configures an awaiter for this value. + /// true to attempt to marshal the continuation back to the captured context; otherwise, false. + public ValueTaskAwaiter ConfigureAwait(bool continueOnCapturedContext) + { + return new ValueTaskAwaiter(this, continueOnCapturedContext: continueOnCapturedContext); + } + + /// Gets a TaskStatus for the debugger to display. + private TaskStatus DebuggerStatus + { + get { return _task == null ? TaskStatus.RanToCompletion : _task.Status; } + } + + /// Gets a result string for the debugger to display. + private string DebuggerResult + { + get + { + return + _task == null ? _result.ToString() : + _task.Status == TaskStatus.RanToCompletion ? _task.Result.ToString() : + SR.Debugger_TaskResultNotAvailable; + } + } + + /// Provides an awaiter for a TaskValue. + public struct ValueTaskAwaiter : ICriticalNotifyCompletion + { + /// The value being awaited. + private readonly ValueTask _value; + /// The value to pass to ConfigureAwait. + private readonly bool _continueOnCapturedContext; + + /// Initializes the awaiter. + /// The value to be awaited. + /// The value to pass to ConfigureAwait. + public ValueTaskAwaiter(ValueTask value, bool continueOnCapturedContext) + { + _value = value; + _continueOnCapturedContext = continueOnCapturedContext; + } + + /// Returns this awaiter. + public ValueTaskAwaiter GetAwaiter() { return this; } + + /// Gets whether the TaskValue has completed. + public bool IsCompleted + { + get { return _value._task == null || _value._task.IsCompleted; } + } + + /// Gets the result of the TaskValue. + public TResult GetResult() + { + return _value._task == null ? + _value._result : + _value._task.GetAwaiter().GetResult(); + } + + /// Schedules the continuation action for this TaskValue. + public void OnCompleted(Action continuation) + { + _value.AsTask().ConfigureAwait(_continueOnCapturedContext).GetAwaiter().OnCompleted(continuation); + } + + /// Schedules the continuation action for this TaskValue. + public void UnsafeOnCompleted(Action continuation) + { + _value.AsTask().ConfigureAwait(_continueOnCapturedContext).GetAwaiter().UnsafeOnCompleted(continuation); + } + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/VoidResult.cs b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/VoidResult.cs new file mode 100644 index 00000000000..07693149d00 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/System/Threading/Tasks/VoidResult.cs @@ -0,0 +1,7 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +namespace System.Threading.Tasks +{ + internal struct VoidResult { } +} diff --git a/src/System.Threading.Tasks.Channels/src/project.json b/src/System.Threading.Tasks.Channels/src/project.json new file mode 100644 index 00000000000..870a25700bd --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/project.json @@ -0,0 +1,14 @@ +{ + "dependencies": { + "System.Collections": "4.0.10-*", + "System.Diagnostics.Debug": "4.0.10-*", + "System.IO": "4.0.10", + "System.Resources.ResourceManager": "4.0.0-*", + "System.Runtime": "4.0.20", + "System.Threading": "4.0.10", + "System.Threading.Tasks": "4.0.10" + }, + "frameworks": { + "dnxcore50": {} + } +} \ No newline at end of file diff --git a/src/System.Threading.Tasks.Channels/src/project.lock.json b/src/System.Threading.Tasks.Channels/src/project.lock.json new file mode 100644 index 00000000000..fa85d3f895c --- /dev/null +++ b/src/System.Threading.Tasks.Channels/src/project.lock.json @@ -0,0 +1,568 @@ +{ + "locked": false, + "version": -9996, + "targets": { + "DNXCore,Version=v5.0": { + "System.Collections/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.20" + }, + "compile": { + "ref/dotnet/System.Collections.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Collections.dll": {} + } + }, + "System.Diagnostics.Debug/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Diagnostics.Debug.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Diagnostics.Debug.dll": {} + } + }, + "System.Globalization/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Globalization.dll": {} + } + }, + "System.IO/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.20", + "System.Text.Encoding": "4.0.0", + "System.Threading.Tasks": "4.0.0" + }, + "compile": { + "ref/dotnet/System.IO.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.IO.dll": {} + } + }, + "System.Private.Uri/4.0.0": { + "runtime": { + "lib/DNXCore50/System.Private.Uri.dll": {} + } + }, + "System.Reflection/4.0.0": { + "dependencies": { + "System.IO": "4.0.0", + "System.Reflection.Primitives": "4.0.0", + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Reflection.dll": {} + } + }, + "System.Reflection.Primitives/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Reflection.Primitives.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Reflection.Primitives.dll": {} + } + }, + "System.Resources.ResourceManager/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Reflection": "4.0.0", + "System.Globalization": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Resources.ResourceManager.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Resources.ResourceManager.dll": {} + } + }, + "System.Runtime/4.0.20": { + "dependencies": { + "System.Private.Uri": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Runtime.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Runtime.dll": {} + } + }, + "System.Text.Encoding/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Text.Encoding.dll": {} + } + }, + "System.Threading/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Threading.Tasks": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Threading.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Threading.dll": {} + } + }, + "System.Threading.Tasks/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Threading.Tasks.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Threading.Tasks.dll": {} + } + } + } + }, + "libraries": { + "System.Collections/4.0.10": { + "sha512": "ux6ilcZZjV/Gp7JEZpe+2V1eTueq6NuoGRM3eZCFuPM25hLVVgCRuea6STW8hvqreIOE59irJk5/ovpA5xQipw==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Collections.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Collections.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/b4f8061406e54dbda8f11b23186be11a.psmdcp", + "ref/dotnet/de/System.Collections.xml", + "ref/dotnet/es/System.Collections.xml", + "ref/dotnet/fr/System.Collections.xml", + "ref/dotnet/it/System.Collections.xml", + "ref/dotnet/ja/System.Collections.xml", + "ref/dotnet/ko/System.Collections.xml", + "ref/dotnet/ru/System.Collections.xml", + "ref/dotnet/System.Collections.dll", + "ref/dotnet/System.Collections.xml", + "ref/dotnet/zh-hans/System.Collections.xml", + "ref/dotnet/zh-hant/System.Collections.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Collections.dll", + "System.Collections.nuspec" + ] + }, + "System.Diagnostics.Debug/4.0.10": { + "sha512": "pi2KthuvI2LWV2c2V+fwReDsDiKpNl040h6DcwFOb59SafsPT/V1fCy0z66OKwysurJkBMmp5j5CBe3Um+ub0g==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Diagnostics.Debug.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Diagnostics.Debug.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/bfb05c26051f4a5f9015321db9cb045c.psmdcp", + "ref/dotnet/de/System.Diagnostics.Debug.xml", + "ref/dotnet/es/System.Diagnostics.Debug.xml", + "ref/dotnet/fr/System.Diagnostics.Debug.xml", + "ref/dotnet/it/System.Diagnostics.Debug.xml", + "ref/dotnet/ja/System.Diagnostics.Debug.xml", + "ref/dotnet/ko/System.Diagnostics.Debug.xml", + "ref/dotnet/ru/System.Diagnostics.Debug.xml", + "ref/dotnet/System.Diagnostics.Debug.dll", + "ref/dotnet/System.Diagnostics.Debug.xml", + "ref/dotnet/zh-hans/System.Diagnostics.Debug.xml", + "ref/dotnet/zh-hant/System.Diagnostics.Debug.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Diagnostics.Debug.dll", + "System.Diagnostics.Debug.nuspec" + ] + }, + "System.Globalization/4.0.0": { + "sha512": "IBJyTo1y7ZtzzoJUA60T1XPvNTyw/wfFmjFoBFtlYfkekIOtD/AzDDIg0YdUa7eNtFEfliED2R7HdppTdU4t5A==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "License.rtf", + "package/services/metadata/core-properties/7256e6303e10459782eff20c3ec90af5.psmdcp", + "ref/dotnet/de/System.Globalization.xml", + "ref/dotnet/es/System.Globalization.xml", + "ref/dotnet/fr/System.Globalization.xml", + "ref/dotnet/it/System.Globalization.xml", + "ref/dotnet/ja/System.Globalization.xml", + "ref/dotnet/ko/System.Globalization.xml", + "ref/dotnet/ru/System.Globalization.xml", + "ref/dotnet/System.Globalization.dll", + "ref/dotnet/System.Globalization.xml", + "ref/dotnet/zh-hans/System.Globalization.xml", + "ref/dotnet/zh-hant/System.Globalization.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/de/System.Globalization.xml", + "ref/netcore50/es/System.Globalization.xml", + "ref/netcore50/fr/System.Globalization.xml", + "ref/netcore50/it/System.Globalization.xml", + "ref/netcore50/ja/System.Globalization.xml", + "ref/netcore50/ko/System.Globalization.xml", + "ref/netcore50/ru/System.Globalization.xml", + "ref/netcore50/System.Globalization.dll", + "ref/netcore50/System.Globalization.xml", + "ref/netcore50/zh-hans/System.Globalization.xml", + "ref/netcore50/zh-hant/System.Globalization.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "System.Globalization.nuspec" + ] + }, + "System.IO/4.0.10": { + "sha512": "kghf1CeYT+W2lw8a50/GxFz5HR9t6RkL4BvjxtTp1NxtEFWywnMA9W8FH/KYXiDNThcw9u/GOViDON4iJFGXIQ==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.IO.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.IO.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/db72fd58a86b4d13a6d2858ebec46705.psmdcp", + "ref/dotnet/de/System.IO.xml", + "ref/dotnet/es/System.IO.xml", + "ref/dotnet/fr/System.IO.xml", + "ref/dotnet/it/System.IO.xml", + "ref/dotnet/ja/System.IO.xml", + "ref/dotnet/ko/System.IO.xml", + "ref/dotnet/ru/System.IO.xml", + "ref/dotnet/System.IO.dll", + "ref/dotnet/System.IO.xml", + "ref/dotnet/zh-hans/System.IO.xml", + "ref/dotnet/zh-hant/System.IO.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.IO.dll", + "System.IO.nuspec" + ] + }, + "System.Private.Uri/4.0.0": { + "sha512": "CtuxaCKcRIvPcsqquVl3mPp79EDZPMr2UogfiFCxCs+t2z1VjbpQsKNs1GHZ8VQetqbk1mr0V1yAfMe6y8CHDA==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Private.Uri.dll", + "lib/netcore50/System.Private.Uri.dll", + "package/services/metadata/core-properties/86377e21a22d44bbba860094428d894c.psmdcp", + "ref/dnxcore50/_._", + "ref/netcore50/_._", + "runtimes/win8-aot/lib/netcore50/System.Private.Uri.dll", + "System.Private.Uri.nuspec" + ] + }, + "System.Reflection/4.0.0": { + "sha512": "g96Rn8XuG7y4VfxPj/jnXroRJdQ8L3iN3k3zqsuzk4k3Nq4KMXARYiIO4BLW4GwX06uQpuYwRMcAC/aF117knQ==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "License.rtf", + "package/services/metadata/core-properties/1e935117d401458384a90c2c69f60bd2.psmdcp", + "ref/dotnet/de/System.Reflection.xml", + "ref/dotnet/es/System.Reflection.xml", + "ref/dotnet/fr/System.Reflection.xml", + "ref/dotnet/it/System.Reflection.xml", + "ref/dotnet/ja/System.Reflection.xml", + "ref/dotnet/ko/System.Reflection.xml", + "ref/dotnet/ru/System.Reflection.xml", + "ref/dotnet/System.Reflection.dll", + "ref/dotnet/System.Reflection.xml", + "ref/dotnet/zh-hans/System.Reflection.xml", + "ref/dotnet/zh-hant/System.Reflection.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/de/System.Reflection.xml", + "ref/netcore50/es/System.Reflection.xml", + "ref/netcore50/fr/System.Reflection.xml", + "ref/netcore50/it/System.Reflection.xml", + "ref/netcore50/ja/System.Reflection.xml", + "ref/netcore50/ko/System.Reflection.xml", + "ref/netcore50/ru/System.Reflection.xml", + "ref/netcore50/System.Reflection.dll", + "ref/netcore50/System.Reflection.xml", + "ref/netcore50/zh-hans/System.Reflection.xml", + "ref/netcore50/zh-hant/System.Reflection.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "System.Reflection.nuspec" + ] + }, + "System.Reflection.Primitives/4.0.0": { + "sha512": "n9S0XpKv2ruc17FSnaiX6nV47VfHTZ1wLjKZlAirUZCvDQCH71mVp+Ohabn0xXLh5pK2PKp45HCxkqu5Fxn/lA==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Reflection.Primitives.dll", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Primitives.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "package/services/metadata/core-properties/7070509f3bfd418d859635361251dab0.psmdcp", + "ref/dotnet/de/System.Reflection.Primitives.xml", + "ref/dotnet/es/System.Reflection.Primitives.xml", + "ref/dotnet/fr/System.Reflection.Primitives.xml", + "ref/dotnet/it/System.Reflection.Primitives.xml", + "ref/dotnet/ja/System.Reflection.Primitives.xml", + "ref/dotnet/ko/System.Reflection.Primitives.xml", + "ref/dotnet/ru/System.Reflection.Primitives.xml", + "ref/dotnet/System.Reflection.Primitives.dll", + "ref/dotnet/System.Reflection.Primitives.xml", + "ref/dotnet/zh-hans/System.Reflection.Primitives.xml", + "ref/dotnet/zh-hant/System.Reflection.Primitives.xml", + "ref/net45/_._", + "ref/netcore50/System.Reflection.Primitives.dll", + "ref/netcore50/System.Reflection.Primitives.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "runtimes/win8-aot/lib/netcore50/System.Reflection.Primitives.dll", + "System.Reflection.Primitives.nuspec" + ] + }, + "System.Resources.ResourceManager/4.0.0": { + "sha512": "qmqeZ4BJgjfU+G2JbrZt4Dk1LsMxO4t+f/9HarNY6w8pBgweO6jT+cknUH7c3qIrGvyUqraBhU45Eo6UtA0fAw==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Resources.ResourceManager.dll", + "lib/net45/_._", + "lib/netcore50/System.Resources.ResourceManager.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "package/services/metadata/core-properties/657a73ee3f09479c9fedb9538ade8eac.psmdcp", + "ref/dotnet/de/System.Resources.ResourceManager.xml", + "ref/dotnet/es/System.Resources.ResourceManager.xml", + "ref/dotnet/fr/System.Resources.ResourceManager.xml", + "ref/dotnet/it/System.Resources.ResourceManager.xml", + "ref/dotnet/ja/System.Resources.ResourceManager.xml", + "ref/dotnet/ko/System.Resources.ResourceManager.xml", + "ref/dotnet/ru/System.Resources.ResourceManager.xml", + "ref/dotnet/System.Resources.ResourceManager.dll", + "ref/dotnet/System.Resources.ResourceManager.xml", + "ref/dotnet/zh-hans/System.Resources.ResourceManager.xml", + "ref/dotnet/zh-hant/System.Resources.ResourceManager.xml", + "ref/net45/_._", + "ref/netcore50/System.Resources.ResourceManager.dll", + "ref/netcore50/System.Resources.ResourceManager.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "runtimes/win8-aot/lib/netcore50/System.Resources.ResourceManager.dll", + "System.Resources.ResourceManager.nuspec" + ] + }, + "System.Runtime/4.0.20": { + "sha512": "X7N/9Bz7jVPorqdVFO86ns1sX6MlQM+WTxELtx+Z4VG45x9+LKmWH0GRqjgKprUnVuwmfB9EJ9DQng14Z7/zwg==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Runtime.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Runtime.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/d1ded52f75da4446b1c962f9292aa3ef.psmdcp", + "ref/dotnet/de/System.Runtime.xml", + "ref/dotnet/es/System.Runtime.xml", + "ref/dotnet/fr/System.Runtime.xml", + "ref/dotnet/it/System.Runtime.xml", + "ref/dotnet/ja/System.Runtime.xml", + "ref/dotnet/ko/System.Runtime.xml", + "ref/dotnet/ru/System.Runtime.xml", + "ref/dotnet/System.Runtime.dll", + "ref/dotnet/System.Runtime.xml", + "ref/dotnet/zh-hans/System.Runtime.xml", + "ref/dotnet/zh-hant/System.Runtime.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Runtime.dll", + "System.Runtime.nuspec" + ] + }, + "System.Text.Encoding/4.0.0": { + "sha512": "AMxFNOXpA6Ab8swULbXuJmoT2K5w6TnV3ObF5wsmEcIHQUJghoZtDVfVHb08O2wW15mOSI1i9Wg0Dx0pY13o8g==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "License.rtf", + "package/services/metadata/core-properties/8740abce9d6a472db9f21ed43f2fb149.psmdcp", + "ref/dotnet/de/System.Text.Encoding.xml", + "ref/dotnet/es/System.Text.Encoding.xml", + "ref/dotnet/fr/System.Text.Encoding.xml", + "ref/dotnet/it/System.Text.Encoding.xml", + "ref/dotnet/ja/System.Text.Encoding.xml", + "ref/dotnet/ko/System.Text.Encoding.xml", + "ref/dotnet/ru/System.Text.Encoding.xml", + "ref/dotnet/System.Text.Encoding.dll", + "ref/dotnet/System.Text.Encoding.xml", + "ref/dotnet/zh-hans/System.Text.Encoding.xml", + "ref/dotnet/zh-hant/System.Text.Encoding.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/de/System.Text.Encoding.xml", + "ref/netcore50/es/System.Text.Encoding.xml", + "ref/netcore50/fr/System.Text.Encoding.xml", + "ref/netcore50/it/System.Text.Encoding.xml", + "ref/netcore50/ja/System.Text.Encoding.xml", + "ref/netcore50/ko/System.Text.Encoding.xml", + "ref/netcore50/ru/System.Text.Encoding.xml", + "ref/netcore50/System.Text.Encoding.dll", + "ref/netcore50/System.Text.Encoding.xml", + "ref/netcore50/zh-hans/System.Text.Encoding.xml", + "ref/netcore50/zh-hant/System.Text.Encoding.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "System.Text.Encoding.nuspec" + ] + }, + "System.Threading/4.0.10": { + "sha512": "0w6pRxIEE7wuiOJeKabkDgeIKmqf4ER1VNrs6qFwHnooEE78yHwi/bKkg5Jo8/pzGLm0xQJw0nEmPXt1QBAIUA==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Threading.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Threading.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/c17c3791d8fa4efbb8aded2ca8c71fbe.psmdcp", + "ref/dotnet/de/System.Threading.xml", + "ref/dotnet/es/System.Threading.xml", + "ref/dotnet/fr/System.Threading.xml", + "ref/dotnet/it/System.Threading.xml", + "ref/dotnet/ja/System.Threading.xml", + "ref/dotnet/ko/System.Threading.xml", + "ref/dotnet/ru/System.Threading.xml", + "ref/dotnet/System.Threading.dll", + "ref/dotnet/System.Threading.xml", + "ref/dotnet/zh-hans/System.Threading.xml", + "ref/dotnet/zh-hant/System.Threading.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Threading.dll", + "System.Threading.nuspec" + ] + }, + "System.Threading.Tasks/4.0.10": { + "sha512": "NOwJGDfk79jR0bnzosbXLVD/PdI8KzBeESoa3CofEM5v9R5EBfcI0Jyf18stx+0IYV9okmDIDxVtxq9TbnR9bQ==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Threading.Tasks.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Threading.Tasks.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/a4ed35f8764a4b68bb39ec8d13b3e730.psmdcp", + "ref/dotnet/de/System.Threading.Tasks.xml", + "ref/dotnet/es/System.Threading.Tasks.xml", + "ref/dotnet/fr/System.Threading.Tasks.xml", + "ref/dotnet/it/System.Threading.Tasks.xml", + "ref/dotnet/ja/System.Threading.Tasks.xml", + "ref/dotnet/ko/System.Threading.Tasks.xml", + "ref/dotnet/ru/System.Threading.Tasks.xml", + "ref/dotnet/System.Threading.Tasks.dll", + "ref/dotnet/System.Threading.Tasks.xml", + "ref/dotnet/zh-hans/System.Threading.Tasks.xml", + "ref/dotnet/zh-hant/System.Threading.Tasks.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Threading.Tasks.dll", + "System.Threading.Tasks.nuspec" + ] + } + }, + "projectFileDependencyGroups": { + "": [ + "System.Collections >= 4.0.10-*", + "System.Diagnostics.Debug >= 4.0.10-*", + "System.IO >= 4.0.10", + "System.Resources.ResourceManager >= 4.0.0-*", + "System.Runtime >= 4.0.20", + "System.Threading >= 4.0.10", + "System.Threading.Tasks >= 4.0.10" + ], + "DNXCore,Version=v5.0": [] + } +} \ No newline at end of file diff --git a/src/System.Threading.Tasks.Channels/tests/BoundedChannelTests.cs b/src/System.Threading.Tasks.Channels/tests/BoundedChannelTests.cs new file mode 100644 index 00000000000..b71ec970d3f --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/BoundedChannelTests.cs @@ -0,0 +1,181 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public class BoundedChannelTests : ChannelTestBase + { + protected override IChannel CreateChannel() + { + return Channel.Create(1); + } + + [Fact] + public async Task Complete_BeforeEmpty_NoWaiters_TriggersCompletion() + { + IChannel c = Channel.Create(1); + Assert.True(c.TryWrite(42)); + c.Complete(); + Assert.False(c.Completion.IsCompleted); + Assert.Equal(42, await c.ReadAsync()); + await c.Completion; + } + + [Fact] + public async Task Complete_BeforeEmpty_WaitingWriters_TriggersCompletion() + { + IChannel c = Channel.Create(1); + Assert.True(c.TryWrite(42)); + Task write2 = c.WriteAsync(43); + c.Complete(); + Assert.Equal(42, await c.ReadAsync()); + await c.Completion; + await Assert.ThrowsAnyAsync(() => write2); + } + + [Theory] + [InlineData(1)] + [InlineData(10)] + [InlineData(10000)] + public void TryWrite_TryRead_Many(int bufferedCapacity) + { + IChannel c = Channel.Create(bufferedCapacity); + + for (int i = 0; i < bufferedCapacity; i++) + { + Assert.True(c.TryWrite(i)); + } + Assert.False(c.TryWrite(bufferedCapacity)); + + int result; + for (int i = 0; i < bufferedCapacity; i++) + { + Assert.True(c.TryRead(out result)); + Assert.Equal(i, result); + } + + Assert.False(c.TryRead(out result)); + Assert.Equal(0, result); + } + + [Theory] + [InlineData(1)] + [InlineData(10)] + [InlineData(10000)] + public void TryWrite_TryRead_OneAtATime(int bufferedCapacity) + { + IChannel c = Channel.Create(bufferedCapacity); + + const int NumItems = 100000; + for (int i = 0; i < NumItems; i++) + { + Assert.True(c.TryWrite(i)); + int result; + Assert.True(c.TryRead(out result)); + Assert.Equal(i, result); + } + } + + [Theory] + [InlineData(1, false)] + [InlineData(10, false)] + [InlineData(10000, false)] + public void SingleProducerConsumer_ConcurrentReadWrite_Success(int bufferedCapacity, bool singleProducerConsumer) + { + IChannel c = Channel.Create(bufferedCapacity, singleProducerConsumer); + + const int NumItems = 10000; + Task.WaitAll( + Task.Run(async () => + { + for (int i = 0; i < NumItems; i++) + { + await c.WriteAsync(i); + } + }), + Task.Run(async () => + { + for (int i = 0; i < NumItems; i++) + { + Assert.Equal(i, await c.ReadAsync()); + } + })); + } + + [Theory] + [InlineData(1, false)] + [InlineData(10, false)] + [InlineData(10000, false)] + [InlineData(1, true)] + [InlineData(10, true)] + [InlineData(10000, true)] + public void ManyProducerConsumer_ConcurrentReadWrite_Success(int bufferedCapacity, bool singleProducerConsumer) + { + IChannel c = Channel.Create(bufferedCapacity, singleProducerConsumer); + + const int NumWriters = 10; + const int NumReaders = 10; + const int NumItems = 10000; + + long readTotal = 0; + int remainingWriters = NumWriters; + int remainingItems = NumItems; + + Task[] tasks = new Task[NumWriters + NumReaders]; + + for (int i = 0; i < NumReaders; i++) + { + tasks[i] = Task.Run(async () => + { + IAsyncEnumerator e = c.GetAsyncEnumerator(); + while (await e.MoveNextAsync()) + { + Interlocked.Add(ref readTotal, e.Current); + } + }); + } + + for (int i = 0; i < NumWriters; i++) + { + tasks[NumReaders + i] = Task.Run(async () => + { + while (true) + { + int value = Interlocked.Decrement(ref remainingItems); + if (value < 0) + { + break; + } + await c.WriteAsync(value + 1); + } + if (Interlocked.Decrement(ref remainingWriters) == 0) + c.Complete(); + }); + } + + Task.WaitAll(tasks); + Assert.Equal((NumItems * (NumItems + 1L)) / 2, readTotal); + } + + [Fact] + public async Task WaitToWriteAsync_AfterFullThenRead_ReturnsTrue() + { + IChannel c = Channel.Create(1); + Assert.True(c.TryWrite(1)); + + Task write1 = c.WaitToWriteAsync(); + Assert.False(write1.IsCompleted); + + Task write2 = c.WaitToWriteAsync(); + Assert.False(write2.IsCompleted); + + Assert.Equal(1, await c.ReadAsync()); + + Assert.True(await write1); + Assert.True(await write2); + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/CaseBuilderTests.cs b/src/System.Threading.Tasks.Channels/tests/CaseBuilderTests.cs new file mode 100644 index 00000000000..fbb4cdf8d7f --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/CaseBuilderTests.cs @@ -0,0 +1,619 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public class CaseBuilderTests : TestBase + { + [Fact] + public void CaseRead_Sync_InvalidArguments_ThrowsArgumentException() + { + Channel.CaseBuilder cb = Channel.CaseRead(Channel.Create(), i => { }); + Assert.Throws("channel", () => cb.CaseRead(null, (Action)null)); + Assert.Throws("channel", () => cb.CaseRead(null, i => { })); + Assert.Throws("action", () => cb.CaseRead(Channel.Create(), (Action)null)); + } + + [Fact] + public void CaseRead_Async_InvalidArguments_ThrowsArgumentException() + { + Channel.CaseBuilder cb = Channel.CaseRead(Channel.Create(), i => { }); + Assert.Throws("channel", () => cb.CaseRead(null, (Func)null)); + Assert.Throws("channel", () => cb.CaseRead(null, i => Task.CompletedTask)); + Assert.Throws("func", () => cb.CaseRead(Channel.Create(), (Func)null)); + } + + [Fact] + public void CaseWrite_Sync_InvalidArguments_ThrowsArgumentException() + { + Channel.CaseBuilder cb = Channel.CaseRead(Channel.Create(), i => { }); + Assert.Throws("channel", () => cb.CaseWrite(null, 0, (Action)null)); + Assert.Throws("channel", () => cb.CaseWrite(null, 0, (Action)delegate { })); + Assert.Throws("action", () => cb.CaseWrite(Channel.Create(), 0, (Action)null)); + } + + [Fact] + public void CaseWrite_Async_InvalidArguments_ThrowsArgumentException() + { + Channel.CaseBuilder cb = Channel.CaseRead(Channel.Create(), i => { }); + Assert.Throws("channel", () => cb.CaseWrite(null, 0, (Func)null)); + Assert.Throws("channel", () => cb.CaseWrite(null, 0, delegate { return Task.CompletedTask; })); + Assert.Throws("func", () => cb.CaseWrite(Channel.Create(), 0, (Func)null)); + } + + [Fact] + public void CaseDefault_Sync_InvalidAction_ThrowsException() + { + Channel.CaseBuilder builder1 = Channel.CaseRead(Channel.Create(), i => { }); + Assert.Throws(() => builder1.CaseDefault((Action)null)); + } + [Fact] + public void CaseDefault_Async_InvalidAction_ThrowsException() + { + Channel.CaseBuilder builder1 = Channel.CaseRead(Channel.Create(), i => Task.CompletedTask); + Assert.Throws(() => builder1.CaseDefault((Func)null)); + } + + [Fact] + public void CaseReadWrite_Sync_CallMultipleTimes_IdempotentResult() + { + Channel.CaseBuilder builder1 = Channel.CaseRead(Channel.Create(), i => { }); + Assert.Same(builder1, builder1.CaseRead(Channel.Create(), i => { })); + Assert.Same(builder1, builder1.CaseWrite(Channel.Create(), "", () => { })); + Assert.Same(builder1, builder1.CaseDefault(() => { })); + + Channel.CaseBuilder builder2 = Channel.CaseWrite(Channel.Create(), 0, () => { }); + Assert.Same(builder2, builder2.CaseRead(Channel.Create(), i => { })); + Assert.Same(builder2, builder2.CaseWrite(Channel.Create(), "", () => { })); + Assert.Same(builder2, builder2.CaseDefault(() => { })); + } + + [Fact] + public void CaseReadWrite_Async_CallMultipleTimes_IdempotentResult() + { + Channel.CaseBuilder builder1 = Channel.CaseRead(Channel.Create(), i => Task.CompletedTask); + Assert.Same(builder1, builder1.CaseRead(Channel.Create(), i => Task.CompletedTask)); + Assert.Same(builder1, builder1.CaseWrite(Channel.Create(), "", () => Task.CompletedTask)); + Assert.Same(builder1, builder1.CaseDefault(() => Task.CompletedTask)); + + Channel.CaseBuilder builder2 = Channel.CaseWrite(Channel.Create(), 0, () => Task.CompletedTask); + Assert.Same(builder2, builder2.CaseRead(Channel.Create(), i => Task.CompletedTask)); + Assert.Same(builder2, builder2.CaseWrite(Channel.Create(), "", () => Task.CompletedTask)); + Assert.Same(builder2, builder2.CaseDefault(() => Task.CompletedTask)); + } + + [Fact] + public void CaseDefault_AlreadyExists_ThrowsException() + { + Channel.CaseBuilder cb = Channel.CaseRead(Channel.Create(), i => { }).CaseDefault(() => { }); + Assert.Throws(() => cb.CaseDefault(() => { })); + Assert.Throws(() => cb.CaseDefault(() => Task.CompletedTask)); + } + + [Fact] + public void SelectAsync_Precanceled_ThrowsCancellationException() + { + IChannel c = Channel.Create(); + Assert.True(c.TryWrite(42)); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + + Task select = Channel + .CaseRead(c, i => { throw new InvalidOperationException(); }) + .SelectAsync(cts.Token); + AssertSynchronouslyCanceled(select, cts.Token); + } + + [Fact] + public async Task SelectAsync_CanceledAfterSelectBeforeData_ThrowsCancellationException() + { + IChannel c = Channel.Create(); + var cts = new CancellationTokenSource(); + + Task select = Channel + .CaseRead(c, i => { throw new InvalidOperationException(); }) + .SelectAsync(cts.Token); + + cts.Cancel(); + + await AssertCanceled(select, cts.Token); + } + + [Fact] + public void SelectAsync_NoChannelsAvailable_SyncDefault_CompletesSynchronously() + { + IChannel c1 = Channel.Create(); + IChannel c2 = Channel.CreateUnbuffered(); + + var tcs = new TaskCompletionSource(); + Task select = Channel + .CaseRead(c1, i => { throw new InvalidOperationException(); }) + .CaseWrite(c2, 42, () => { throw new InvalidOperationException(); }) + .CaseDefault(() => tcs.SetResult(84)) + .SelectAsync(); + + Assert.Equal(TaskStatus.RanToCompletion, select.Status); + Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); + Assert.True(select.Result); + Assert.Equal(84, tcs.Task.Result); + } + + [Fact] + public void SelectAsync_NoChannelsAvailable_AsyncDefault_CompletesSynchronously() + { + IChannel c1 = Channel.Create(); + IChannel c2 = Channel.CreateUnbuffered(); + + var tcs = new TaskCompletionSource(); + Task select = Channel + .CaseRead(c1, i => { throw new InvalidOperationException(); }) + .CaseWrite(c2, 42, () => { throw new InvalidOperationException(); }) + .CaseDefault(() => { tcs.SetResult(84); return Task.CompletedTask; }) + .SelectAsync(); + + Assert.Equal(TaskStatus.RanToCompletion, select.Status); + Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); + Assert.True(select.Result); + Assert.Equal(84, tcs.Task.Result); + } + + [Fact] + public async Task SelectAsync_NoChannelsAvailable_AsyncDefault_CompletesAsynchronously() + { + IChannel c1 = Channel.Create(); + IChannel c2 = Channel.CreateUnbuffered(); + + var tcs = new TaskCompletionSource(); + Task select = Channel + .CaseRead(c1, i => { throw new InvalidOperationException(); }) + .CaseWrite(c2, 42, () => { throw new InvalidOperationException(); }) + .CaseDefault(async () => { await Task.Yield(); tcs.SetResult(84); }) + .SelectAsync(); + + Assert.True(await select); + Assert.Equal(84, tcs.Task.Result); + } + + [Fact] + public async Task SelectAsync_NoChannelsAvailable_SyncDefault_ThrowsSynchronously() + { + IChannel c1 = Channel.Create(); + IChannel c2 = Channel.CreateUnbuffered(); + + Task select = Channel + .CaseRead(c1, i => { throw new InvalidOperationException(); }) + .CaseWrite(c2, 42, () => { throw new InvalidOperationException(); }) + .CaseDefault(new Action(() => { throw new FormatException(); })) + .SelectAsync(); + + Assert.True(select.IsCompleted); + await Assert.ThrowsAsync(() => select); + } + + [Fact] + public async Task SelectAsync_NoChannelsAvailable_AsyncDefault_ThrowsSynchronously() + { + IChannel c1 = Channel.Create(); + IChannel c2 = Channel.CreateUnbuffered(); + + Task select = Channel + .CaseRead(c1, i => { throw new InvalidOperationException(); }) + .CaseWrite(c2, 42, () => { throw new InvalidOperationException(); }) + .CaseDefault(new Func(() => { throw new FormatException(); })) + .SelectAsync(); + + Assert.True(select.IsCompleted); + await Assert.ThrowsAsync(() => select); + } + + [Fact] + public async Task SelectAsync_NoChannelsAvailable_AsyncDefault_ThrowsAsynchronously() + { + IChannel c1 = Channel.Create(); + IChannel c2 = Channel.CreateUnbuffered(); + + Task select = Channel + .CaseRead(c1, i => { throw new InvalidOperationException(); }) + .CaseWrite(c2, 42, () => { throw new InvalidOperationException(); }) + .CaseDefault(async () => { await Task.Yield(); throw new FormatException(); }) + .SelectAsync(); + + await Assert.ThrowsAsync(() => select); + } + + [Fact] + public async Task SelectAsync_AllChannelsCompletedBefore_ReturnsFalse() + { + IChannel c1 = Channel.Create(); + IChannel c2 = Channel.CreateUnbuffered(); + c1.Complete(); + c2.Complete(); + + Task select = Channel + .CaseRead(c1, i => { throw new InvalidOperationException(); }) + .CaseWrite(c2, 42, () => { throw new InvalidOperationException(); }) + .SelectAsync(); + Assert.False(await select); + } + + [Fact] + public async Task SelectAsync_AllChannelsCompletedAfter_ReturnsFalse() + { + IChannel c1 = Channel.Create(); + IChannel c2 = Channel.CreateUnbuffered(); + + Task select = Channel + .CaseRead(c1, i => { throw new InvalidOperationException(); }) + .CaseWrite(c2, 42, () => { throw new InvalidOperationException(); }) + .SelectAsync(); + + c1.Complete(); + c2.Complete(); + + Assert.False(await select); + } + + [Fact] + public async Task SelectAsync_SingleCaseRead_Sync_DataAlreadyAvailable() + { + IChannel c = Channel.Create(); + Assert.True(c.TryWrite(42)); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseRead(c, i => tcs.SetResult(i)).SelectAsync(); + + Assert.True(select.IsCompleted); + Assert.True(await select); + + Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); + Assert.Equal(42, await tcs.Task); + } + + [Fact] + public async Task SelectAsync_SingleCaseRead_Async_DataAlreadyAvailable_CompletesSynchronously() + { + IChannel c = Channel.Create(); + Assert.True(c.TryWrite(42)); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseRead(c, i => { tcs.SetResult(i); return Task.CompletedTask; }).SelectAsync(); + + Assert.True(select.IsCompleted); + Assert.True(await select); + + Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); + Assert.Equal(42, await tcs.Task); + } + + [Fact] + public async Task SelectAsync_SingleCaseRead_Async_DataAlreadyAvailable_CompletesAsynchronously() + { + IChannel c = Channel.Create(); + Assert.True(c.TryWrite(42)); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseRead(c, async i => { await Task.Yield(); tcs.SetResult(i); }).SelectAsync(); + + Assert.True(await select); + + Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); + Assert.Equal(42, await tcs.Task); + } + + [Fact] + public async Task SelectAsync_SingleCaseRead_Sync_DataNotAlreadyAvailable() + { + IChannel c = Channel.CreateUnbuffered(); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseRead(c, i => tcs.SetResult(i)).SelectAsync(); + Assert.False(select.IsCompleted); + + Task write = c.WriteAsync(42); + + Assert.True(await select); + Assert.Equal(42, await tcs.Task); + } + + [Fact] + public async Task SelectAsync_SingleCaseRead_Async_DataNotAlreadyAvailable_CompletesSynchronously() + { + IChannel c = Channel.CreateUnbuffered(); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseRead(c, i => { tcs.SetResult(i); return Task.CompletedTask; }).SelectAsync(); + Assert.False(select.IsCompleted); + + Task write = c.WriteAsync(42); + + Assert.True(await select); + Assert.Equal(42, await tcs.Task); + } + + [Fact] + public async Task SelectAsync_SingleCaseRead_Async_DataNotAlreadyAvailable_CompletesAsynchronously() + { + IChannel c = Channel.CreateUnbuffered(); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseRead(c, async i => { await Task.Yield(); tcs.SetResult(i); }).SelectAsync(); + Assert.False(select.IsCompleted); + + Task write = c.WriteAsync(42); + + Assert.True(await select); + Assert.Equal(42, await tcs.Task); + } + + [Fact] + public async Task SelectAsync_SingleCaseWrite_Sync_SpaceAlreadyAvailable_CompletesSynchronously() + { + IChannel c = Channel.Create(); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseWrite(c, 42, () => tcs.SetResult(1)).SelectAsync(); + + Assert.True(select.IsCompleted); + Assert.True(await select); + + Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); + Assert.Equal(1, await tcs.Task); + + int result; + Assert.True(c.TryRead(out result)); + Assert.Equal(42, result); + } + + [Fact] + public async Task SelectAsync_SingleCaseWrite_Async_SpaceAlreadyAvailable_CompletesSynchronously() + { + IChannel c = Channel.Create(); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseWrite(c, 42, () => { tcs.SetResult(1); return Task.CompletedTask; }).SelectAsync(); + + Assert.True(select.IsCompleted); + Assert.True(await select); + + Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); + Assert.Equal(1, await tcs.Task); + + int result; + Assert.True(c.TryRead(out result)); + Assert.Equal(42, result); + } + + [Fact] + public async Task SelectAsync_SingleCaseWrite_Async_SpaceAlreadyAvailable_CompletesAsynchronously() + { + IChannel c = Channel.Create(); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseWrite(c, 42, async () => { await Task.Yield(); tcs.SetResult(1); }).SelectAsync(); + + Assert.True(await select); + + Assert.Equal(TaskStatus.RanToCompletion, tcs.Task.Status); + Assert.Equal(1, await tcs.Task); + + int result; + Assert.True(c.TryRead(out result)); + Assert.Equal(42, result); + } + + [Fact] + public async Task SelectAsync_SingleCaseWrite_Sync_SpaceNotAlreadyAvailable() + { + IChannel c = Channel.CreateUnbuffered(); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseWrite(c, 42, () => tcs.SetResult(1)).SelectAsync(); + + Assert.False(select.IsCompleted); + + Task read = c.ReadAsync().AsTask(); + + Assert.True(await select); + Assert.Equal(42, await read); + Assert.Equal(1, await tcs.Task); + } + + [Fact] + public async Task SelectAsync_SingleCaseWrite_Async_SpaceNotAlreadyAvailable_CompletesSynchronously() + { + IChannel c = Channel.CreateUnbuffered(); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseWrite(c, 42, () => { tcs.SetResult(1); return Task.CompletedTask; }).SelectAsync(); + + Assert.False(select.IsCompleted); + + Task read = c.ReadAsync().AsTask(); + + Assert.True(await select); + Assert.Equal(42, await read); + Assert.Equal(1, await tcs.Task); + } + + [Fact] + public async Task SelectAsync_SingleCaseWrite_Async_SpaceNotAlreadyAvailable_CompletesAsynchronously() + { + IChannel c = Channel.CreateUnbuffered(); + + var tcs = new TaskCompletionSource(); + Task select = Channel.CaseWrite(c, 42, async () => { await Task.Yield(); tcs.SetResult(1); }).SelectAsync(); + + Assert.False(select.IsCompleted); + + Task read = c.ReadAsync().AsTask(); + + Assert.True(await select); + Assert.Equal(42, await read); + Assert.Equal(1, await tcs.Task); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SelectAsync_CaseRead_Sync_ThrowsSynchronously(bool before) + { + IChannel c = Channel.CreateUnbuffered(); + Task write; + if (before) + write = c.WriteAsync(42); + Task t = Channel.CaseRead(c, new Action(i => { throw new FormatException(); })).SelectAsync(); + if (!before) + write = c.WriteAsync(42); + await Assert.ThrowsAsync(() => t); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SelectAsync_CaseRead_Async_DataAlreadyAvailable_ThrowsSynchronously(bool before) + { + IChannel c = Channel.CreateUnbuffered(); + Task write; + if (before) + write = c.WriteAsync(42); + Task t = Channel.CaseRead(c, new Func(i => { throw new FormatException(); })).SelectAsync(); + if (!before) + write = c.WriteAsync(42); + await Assert.ThrowsAsync(() => t); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SelectAsync_CaseRead_Async_DataAlreadyAvailable_ThrowsAsynchronously(bool before) + { + IChannel c = Channel.CreateUnbuffered(); + Task write; + if (before) + write = c.WriteAsync(42); + Task t = Channel.CaseRead(c, async i => { await Task.Yield(); throw new FormatException(); }).SelectAsync(); + if (!before) + write = c.WriteAsync(42); + await Assert.ThrowsAsync(() => t); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SelectAsync_CaseWrite_Sync_ThrowsSynchronously(bool before) + { + IChannel c = Channel.CreateUnbuffered(); + Task read; + if (before) + read = c.ReadAsync().AsTask(); + Task t = Channel.CaseWrite(c, 42, new Action(() => { throw new FormatException(); })).SelectAsync(); + if (!before) + read = c.ReadAsync().AsTask(); + await Assert.ThrowsAsync(() => t); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SelectAsync_CaseWrite_Async_ThrowsSynchronously(bool before) + { + IChannel c = Channel.CreateUnbuffered(); + Task read; + if (before) + read = c.ReadAsync().AsTask(); + Task t = Channel.CaseWrite(c, 42, new Func(() => { throw new FormatException(); })).SelectAsync(); + if (!before) + read = c.ReadAsync().AsTask(); + await Assert.ThrowsAsync(() => t); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task SelectAsync_CaseWrite_Async_ThrowsAsynchronously(bool before) + { + IChannel c = Channel.CreateUnbuffered(); + Task read; + if (before) + read = c.ReadAsync().AsTask(); + Task t = Channel.CaseWrite(c, 42, async () => { await Task.Yield(); throw new FormatException(); }).SelectAsync(); + if (!before) + read = c.ReadAsync().AsTask(); + await Assert.ThrowsAsync(() => t); + } + + [Fact] + public void SelectUntilAsync_InvalidArguments_ThrowsExceptions() + { + Channel.CaseBuilder cb = Channel.CaseRead(Channel.Create(), i => { }); + Assert.Throws(() => { cb.SelectUntilAsync(null); }); + } + + [Theory] + [InlineData(false, 100, 150)] + [InlineData(true, 100, 150)] + [InlineData(false, 100, 100)] + [InlineData(true, 100, 100)] + [InlineData(false, 100, 99)] + [InlineData(true, 100, 99)] + [InlineData(false, 100, 1)] + [InlineData(true, 100, 1)] + [InlineData(false, 100, 0)] + [InlineData(true, 100, 0)] + public async Task SelectUntilAsync_ProcessUntilAllDataExhausted_Success(bool dataAvailableBefore, int numItems, int maxIterations) + { + IChannel c1 = Channel.Create(); + IChannel c2 = Channel.Create(); + IChannel c3 = Channel.Create(); + + int delegatesInvoked = 0; + Task select = null; + + if (!dataAvailableBefore) + { + select = Channel + .CaseRead(c1, i => { Interlocked.Increment(ref delegatesInvoked); }) + .CaseRead(c2, s => { Interlocked.Increment(ref delegatesInvoked); }) + .CaseRead(c3, d => { Interlocked.Increment(ref delegatesInvoked); }) + .SelectUntilAsync(i => i < maxIterations); + } + + for (int i = 0; i < numItems; i++) + { + switch (i % 3) + { + case 0: + Assert.True(c1.TryWrite(i)); + break; + case 1: + Assert.True(c2.TryWrite(i.ToString())); + break; + case 2: + Assert.True(c3.TryWrite(i)); + break; + } + } + + c1.Complete(); + c2.Complete(); + c3.Complete(); + + if (dataAvailableBefore) + { + select = Channel + .CaseRead(c1, i => { Interlocked.Increment(ref delegatesInvoked); }) + .CaseRead(c2, s => { Interlocked.Increment(ref delegatesInvoked); }) + .CaseRead(c3, d => { Interlocked.Increment(ref delegatesInvoked); }) + .SelectUntilAsync(i => i < maxIterations); + } + + int expected = Math.Min(numItems, maxIterations); + Assert.Equal(expected, await select); + Assert.Equal(expected, delegatesInvoked); + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/ChannelTestBase.cs b/src/System.Threading.Tasks.Channels/tests/ChannelTestBase.cs new file mode 100644 index 00000000000..e6ec6e33d1e --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/ChannelTestBase.cs @@ -0,0 +1,599 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using System.Linq; +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public abstract class ChannelTestBase : TestBase + { + protected abstract IChannel CreateChannel(); + + protected virtual bool RequiresSingleReaderWriter { get { return false; } } + + [Fact] + public void ValidateDebuggerAttributes() + { + IChannel c = CreateChannel(); + for (int i = 1; i <= 10; i++) + { + c.WriteAsync(i); + } + DebuggerAttributes.ValidateDebuggerDisplayReferences(c); + DebuggerAttributes.ValidateDebuggerTypeProxyProperties(c); + } + + [Fact] + public void Completion_Idempotent() + { + IChannel c = CreateChannel(); + + Task completion = c.Completion; + Assert.Equal(TaskStatus.WaitingForActivation, completion.Status); + + Assert.Same(completion, c.Completion); + c.Complete(); + Assert.Same(completion, c.Completion); + + Assert.Equal(TaskStatus.RanToCompletion, completion.Status); + } + + [Fact] + public async Task Complete_AfterEmpty_NoWaiters_TriggersCompletion() + { + IChannel c = CreateChannel(); + c.Complete(); + await c.Completion; + } + + [Fact] + public async Task Complete_AfterEmpty_WaitingReader_TriggersCompletion() + { + IChannel c = CreateChannel(); + Task r = c.ReadAsync().AsTask(); + c.Complete(); + await c.Completion; + await Assert.ThrowsAnyAsync(() => r); + } + + [Fact] + public async Task Complete_BeforeEmpty_WaitingReaders_TriggersCompletion() + { + IChannel c = Channel.Create(1); + Task read = c.ReadAsync().AsTask(); + c.Complete(); + await c.Completion; + await Assert.ThrowsAnyAsync(() => read); + } + + [Fact] + public void Complete_Twice_ThrowsInvalidOperationException() + { + IChannel c = CreateChannel(); + c.Complete(); + Assert.ThrowsAny(() => c.Complete()); + } + + [Fact] + public void SingleProducerConsumer_ConcurrentReadWrite_Success() + { + IChannel c = Channel.Create(); + + const int NumItems = 100000; + Task.WaitAll( + Task.Run(async () => + { + for (int i = 0; i < NumItems; i++) + { + await c.WriteAsync(i); + } + }), + Task.Run(async () => + { + for (int i = 0; i < NumItems; i++) + { + Assert.Equal(i, await c.ReadAsync()); + } + })); + } + + [Fact] + public void ManyProducerConsumer_ConcurrentReadWrite_Success() + { + if (RequiresSingleReaderWriter) + return; + + IChannel c = CreateChannel(); + + const int NumWriters = 10; + const int NumReaders = 10; + const int NumItems = 10000; + + long readTotal = 0; + int remainingWriters = NumWriters; + int remainingItems = NumItems; + + Task[] tasks = new Task[NumWriters + NumReaders]; + + for (int i = 0; i < NumReaders; i++) + { + tasks[i] = Task.Run(async () => + { + IAsyncEnumerator e = c.GetAsyncEnumerator(); + while (await e.MoveNextAsync()) + { + Interlocked.Add(ref readTotal, e.Current); + } + }); + } + + for (int i = 0; i < NumWriters; i++) + { + tasks[NumReaders + i] = Task.Run(async () => + { + while (true) + { + int value = Interlocked.Decrement(ref remainingItems); + if (value < 0) + { + break; + } + await c.WriteAsync(value + 1); + } + if (Interlocked.Decrement(ref remainingWriters) == 0) + c.Complete(); + }); + } + + Task.WaitAll(tasks); + Assert.Equal((NumItems * (NumItems + 1L)) / 2, readTotal); + } + + [Fact] + public void WaitToReadAsync_DataAvailableBefore_CompletesSynchronously() + { + IChannel c = CreateChannel(); + Task write = c.WriteAsync(42); + Task read = c.WaitToReadAsync(); + Assert.Equal(TaskStatus.RanToCompletion, read.Status); + } + + [Fact] + public void WaitToReadAsync_DataAvailableAfter_CompletesAsynchronously() + { + IChannel c = CreateChannel(); + Task read = c.WaitToReadAsync(); + Assert.False(read.IsCompleted); + Task write = c.WriteAsync(42); + Assert.True(read.Result); + } + + [Fact] + public void WaitToReadAsync_AfterComplete_SynchronouslyCompletes() + { + IChannel c = CreateChannel(); + c.Complete(); + Task read = c.WaitToReadAsync(); + Assert.Equal(TaskStatus.RanToCompletion, read.Status); + Assert.False(read.Result); + } + + [Fact] + public void WaitToReadAsync_BeforeComplete_AsynchronouslyCompletes() + { + IChannel c = CreateChannel(); + Task read = c.WaitToReadAsync(); + Assert.False(read.IsCompleted); + c.Complete(); + Assert.False(read.Result); + } + + [Fact] + public void WaitToWriteAsync_AfterComplete_SynchronouslyCompletes() + { + IChannel c = CreateChannel(); + c.Complete(); + Task write = c.WaitToWriteAsync(); + Assert.Equal(TaskStatus.RanToCompletion, write.Status); + Assert.False(write.Result); + } + + [Fact] + public void WaitToWriteAsync_SpaceAvailableBefore_CompletesSynchronously() + { + IChannel c = CreateChannel(); + ValueTask read = c.ReadAsync(); + Task write = c.WaitToWriteAsync(); + Assert.Equal(TaskStatus.RanToCompletion, write.Status); + } + + [Fact] + public void WaitToWriteAsync_SpaceAvailableAfter_CompletesSynchronously() + { + IChannel c = CreateChannel(); + Task write = c.WaitToWriteAsync(); + ValueTask read = c.ReadAsync(); + Assert.True(write.Result); + } + + [Fact] + public void TryRead_DataAvailable_Success() + { + IChannel c = CreateChannel(); + Task write = c.WriteAsync(42); + int result; + Assert.True(c.TryRead(out result)); + Assert.Equal(42, result); + } + + [Fact] + public async Task ReadAsync_DataAvailable_Success() + { + IChannel c = CreateChannel(); + Task write = c.WriteAsync(42); + Assert.Equal(42, await c.ReadAsync()); + } + + [Fact] + public void TryRead_AfterComplete_ReturnsFalse() + { + IChannel c = CreateChannel(); + c.Complete(); + int result; + Assert.False(c.TryRead(out result)); + } + + [Fact] + public void TryWrite_AfterComplete_ReturnsFalse() + { + IChannel c = CreateChannel(); + c.Complete(); + Assert.False(c.TryWrite(42)); + } + + [Fact] + public async Task WriteAsync_AfterComplete_ThrowsException() + { + IChannel c = CreateChannel(); + c.Complete(); + await Assert.ThrowsAnyAsync(() => c.WriteAsync(42)); + } + + [Fact] + public async Task ReadAsync_AfterComplete_ThrowsException() + { + IChannel c = CreateChannel(); + c.Complete(); + await Assert.ThrowsAnyAsync(() => c.ReadAsync().AsTask()); + } + + [Fact] + public async Task Complete_WithException_PropagatesToCompletion() + { + IChannel c = CreateChannel(); + FormatException exc = new FormatException(); + c.Complete(exc); + Assert.Same(exc, await Assert.ThrowsAsync(() => c.Completion)); + } + + [Fact] + public async Task Complete_WithCancellationException_PropagatesToCompletion() + { + IChannel c = CreateChannel(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + Exception exc = null; + try { cts.Token.ThrowIfCancellationRequested(); } + catch (Exception e) { exc = e; } + + c.Complete(exc); + await AssertCanceled(c.Completion, cts.Token); + } + + [Fact] + public async Task Complete_WithException_PropagatesToExistingReader() + { + IChannel c = CreateChannel(); + Task read = c.ReadAsync().AsTask(); + FormatException exc = new FormatException(); + c.Complete(exc); + Assert.Same(exc, await Assert.ThrowsAsync(() => read)); + } + + [Fact] + public async Task Complete_WithException_PropagatesToNewReader() + { + IChannel c = CreateChannel(); + FormatException exc = new FormatException(); + c.Complete(exc); + Task read = c.ReadAsync().AsTask(); + Assert.Same(exc, await Assert.ThrowsAsync(() => read)); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + public void ManyWriteAsync_ThenManyReadAsync_Success(int mode) + { + if (RequiresSingleReaderWriter) + return; + + IChannel c = CreateChannel(); + + const int NumItems = 2000; + + Task[] writers = new Task[NumItems]; + for (int i = 0; i < writers.Length; i++) + { + writers[i] = c.WriteAsync(i); + } + + Task[] readers = new Task[NumItems]; + for (int i = 0; i < readers.Length; i++) + { + switch (mode) + { + case 0: + readers[i] = c.ReadAsync().AsTask(); + break; + case 1: + int result; + Assert.True(c.TryRead(out result)); + readers[i] = Task.FromResult(result); + break; + } + } + + Task.WaitAll(readers); + Task.WaitAll(writers); + + for (int i = 0; i < readers.Length; i++) + { + Assert.Equal(i, readers[i].Result); + } + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + public void ManyReadAsync_ThenManyWriteAsync_Success(int mode) + { + if (RequiresSingleReaderWriter) + return; + + IChannel c = CreateChannel(); + + const int NumItems = 2000; + + Task[] readers = new Task[NumItems]; + for (int i = 0; i < readers.Length; i++) + { + readers[i] = c.ReadAsync().AsTask(); + } + + Task[] writers = new Task[NumItems]; + for (int i = 0; i < writers.Length; i++) + { + switch (mode) + { + case 0: + writers[i] = c.WriteAsync(i); + break; + case 1: + Assert.True(c.TryWrite(i)); + writers[i] = Task.CompletedTask; + break; + } + } + + Task.WaitAll(readers); + Task.WaitAll(writers); + + for (int i = 0; i < readers.Length; i++) + { + Assert.Equal(i, readers[i].Result); + } + } + + [Fact] + public void Precancellation_Reading_ReturnsCanceledImmediately() + { + IChannel c = CreateChannel(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + AssertSynchronouslyCanceled(c.ReadAsync(cts.Token).AsTask(), cts.Token); + AssertSynchronouslyCanceled(c.WaitToReadAsync(cts.Token), cts.Token); + } + + [Fact] + public void Precancellation_Writing_ReturnsCanceledImmediately() + { + IChannel c = CreateChannel(); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + AssertSynchronouslyCanceled(c.WriteAsync(42, cts.Token), cts.Token); + AssertSynchronouslyCanceled(c.WaitToWriteAsync(cts.Token), cts.Token); + } + + [Fact] + public async Task Await_CompletedChannel_Throws() + { + IChannel c = CreateChannel(); + c.Complete(); + await Assert.ThrowsAnyAsync(async () => await c); + } + + [Fact] + public async Task Await_ChannelAfterExistingData_ReturnsData() + { + IChannel c = CreateChannel(); + Task write = c.WriteAsync(42); + Assert.Equal(42, await c); + Assert.Equal(TaskStatus.RanToCompletion, write.Status); + } + + [Fact] + public async Task SelectAsync_CaseReadBeforeAvailable_Success() + { + if (RequiresSingleReaderWriter) + return; + + IChannel c1 = CreateChannel(); + IChannel c2 = CreateChannel(); + IChannel c3 = CreateChannel(); + + int total1 = 0, total2 = 0, total3 = 0; + int expectedTotal1 = 0, expectedTotal2 = 0, expectedTotal3 = 0; + + var selects = new Task[12]; + for (int i = 0; i < selects.Length; i++) + { + selects[i] = Channel + .CaseRead(c1, item => Interlocked.Add(ref total1, item)) + .CaseRead(c2, item => { Interlocked.Add(ref total2, item); return Task.CompletedTask; }) + .CaseRead(c3, async item => { await Task.Yield(); Interlocked.Add(ref total3, item); }) + .SelectAsync(); + } + + var writes = new Task[selects.Length]; + for (int i = 0; i < selects.Length; i++) + { + switch (i % 3) + { + case 0: + writes[i] = c1.WriteAsync(i); + expectedTotal1 += i; + break; + case 1: + writes[i] = c2.WriteAsync(i); + expectedTotal2 += i; + break; + case 2: + writes[i] = c3.WriteAsync(i); + expectedTotal3 += i; + break; + } + } + + await Task.WhenAll(selects); + Assert.All(writes, write => Assert.Equal(TaskStatus.RanToCompletion, write.Status)); + Assert.All(selects, select => Assert.Equal(TaskStatus.RanToCompletion, select.Status)); + Assert.Equal(selects.Length, selects.Count(s => s.Result)); + + Assert.Equal(expectedTotal1, total1); + Assert.Equal(expectedTotal2, total2); + Assert.Equal(expectedTotal3, total3); + } + + [Fact] + public async Task TryWrite_BlockedReader_Success() + { + IChannel c = CreateChannel(); + Task read = c.ReadAsync().AsTask(); + Assert.False(read.IsCompleted); + Assert.True(c.TryWrite(42)); + Assert.Equal(42, await read); + } + + [Fact] + public void Write_WaitToReadAsync_CompletesSynchronously() + { + IChannel c = CreateChannel(); + c.WriteAsync(42); + AssertSynchronousTrue(c.WaitToReadAsync()); + } + + [Fact] + public void Read_WaitToWriteAsync_CompletesSynchronously() + { + IChannel c = CreateChannel(); + c.ReadAsync(); + AssertSynchronousTrue(c.WaitToWriteAsync()); + } + + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task AsObservable_DataWritten(bool endWithError) + { + IChannel c = CreateChannel(); + IObserver o = c.AsObserver(); + + Task reader = Task.Run(async () => + { + int received = 0; + IAsyncEnumerator e = c.GetAsyncEnumerator(); + while (await e.MoveNextAsync()) + { + Assert.Equal(received++, e.Current); + } + }); + + for (int i = 0; i < 10; i++) + { + o.OnNext(i); + } + + if (endWithError) + { + o.OnError(new FormatException()); + await Assert.ThrowsAsync(() => reader); + } + else + { + o.OnCompleted(); + await reader; + } + } + + [Theory] + [InlineData(1, false)] + [InlineData(1, true)] + [InlineData(5, false)] + [InlineData(5, true)] + public async Task AsObserver_AllDataPushed(int numSubscribers, bool completeWithError) + { + if (RequiresSingleReaderWriter) + return; + + IChannel c = CreateChannel(); + + int received = 0; + var tcs = new TaskCompletionSource(); + + IObservable o = c.AsObservable(); + for (int s = 0; s < numSubscribers; s++) + { + o.Subscribe(new DelegateObserver + { + OnNextDelegate = i => received += i, + OnCompletedDelegate = () => tcs.TrySetResult(null), + OnErrorDelegate = e => tcs.TrySetResult(e), + }); + } + + Task[] writes = new Task[10]; + for (int i = 0; i < writes.Length; i++) + { + writes[i] = c.WriteAsync(i + 1); + } + await Task.WhenAll(writes); + + c.Complete(completeWithError ? new FormatException() : null); + + Exception result = await tcs.Task; + if (completeWithError) + Assert.IsType(result); + else + Assert.Null(result); + + Assert.Equal(numSubscribers * (writes.Length * (writes.Length + 1)) / 2, received); + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/ChannelTests.cs b/src/System.Threading.Tasks.Channels/tests/ChannelTests.cs new file mode 100644 index 00000000000..48332b76c77 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/ChannelTests.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public class ChannelTests + { + [Fact] + public void Unbounded_CorrectValue() + { + Assert.Equal(-1, Channel.Unbounded); + } + + [Theory] + [InlineData(0)] + [InlineData(-2)] + public void Create_InvalidBufferSizes_ThrowArgumentExceptions(int bufferedCapacity) + { + Assert.Throws("bufferedCapacity", () => Channel.Create(bufferedCapacity)); + Assert.Throws("bufferedCapacity", () => Channel.Create(bufferedCapacity, false)); + Assert.Throws("bufferedCapacity", () => Channel.Create(bufferedCapacity, true)); + } + + [Theory] + [InlineData(-1)] + [InlineData(1)] + public void Create_ValidBufferSizes_Success(int bufferedCapacity) + { + Assert.NotNull(Channel.Create(bufferedCapacity)); + Assert.NotNull(Channel.Create(bufferedCapacity, false)); + Assert.NotNull(Channel.Create(bufferedCapacity, true)); + } + + [Fact] + public void CreateUnbuffered_Success() + { + Assert.NotNull(Channel.CreateUnbuffered()); + Assert.NotNull(Channel.CreateUnbuffered()); + } + + [Fact] + public void ReadFromStream_InvalidStream_ThrowsArgumentExceptions() + { + Assert.Throws("source", () => Channel.ReadFromStream(null)); + Assert.Throws("source", () => Channel.ReadFromStream(new CanReadFalseStream())); + } + + [Fact] + public void WriteToStream_InvalidStream_ThrowsArgumentExceptions() + { + Assert.Throws("destination", () => Channel.WriteToStream(null)); + Assert.Throws("destination", () => Channel.WriteToStream(new MemoryStream(Array.Empty(), writable: false))); + } + + [Fact] + public void CreateFromEnumerable_InvalidSource_ThrowsArgumentException() + { + Assert.Throws("source", () => Channel.CreateFromEnumerable(null)); + } + + [Fact] + public void AsObservable_InvalidSource_ThrowsArgumentException() + { + Assert.Throws("source", () => Channel.AsObservable(null)); + } + + [Fact] + public void AsObserver_InvalidTarget_ThrowsArgumentException() + { + Assert.Throws("target", () => Channel.AsObserver(null)); + } + + [Fact] + public void AsObservable_SameSource_Idempotent() + { + IChannel c = Channel.Create(); + Assert.Same(c.AsObservable(), c.AsObservable()); + } + + [Fact] + public void GetAwaiter_InvalidSource_ThrowsArgumentException() + { + Assert.Throws("channel", () => Channel.GetAwaiter(null)); + } + + [Fact] + public void GetAsyncEnumerator_InvalidSource_ThrowsArgumentException() + { + Assert.Throws("channel", () => Channel.GetAsyncEnumerator(null)); + Assert.Throws("channel", () => Channel.GetAsyncEnumerator(null, CancellationToken.None)); + } + + [Fact] + public void CaseRead_Sync_InvalidArguments_ThrowsArgumentException() + { + Assert.Throws("channel", () => Channel.CaseRead(null, (Action)null)); + Assert.Throws("channel", () => Channel.CaseRead(null, i => { })); + Assert.Throws("action", () => Channel.CaseRead(Channel.Create(), (Action)null)); + } + + [Fact] + public void CaseRead_Async_InvalidArguments_ThrowsArgumentException() + { + Assert.Throws("channel", () => Channel.CaseRead(null, (Func)null)); + Assert.Throws("channel", () => Channel.CaseRead(null, i => Task.CompletedTask)); + Assert.Throws("func", () => Channel.CaseRead(Channel.Create(), (Func)null)); + } + + [Fact] + public void CaseWrite_Sync_InvalidArguments_ThrowsArgumentException() + { + Assert.Throws("channel", () => Channel.CaseWrite(null, 0, (Action)null)); + Assert.Throws("channel", () => Channel.CaseWrite(null, 0, (Action)delegate { })); + Assert.Throws("action", () => Channel.CaseWrite(Channel.Create(), 0, (Action)null)); + } + + [Fact] + public void CaseWrite_Async_InvalidArguments_ThrowsArgumentException() + { + Assert.Throws("channel", () => Channel.CaseWrite(null, 0, (Func)null)); + Assert.Throws("channel", () => Channel.CaseWrite(null, 0, delegate { return Task.CompletedTask; })); + Assert.Throws("func", () => Channel.CaseWrite(Channel.Create(), 0, (Func)null)); + } + + [Fact] + public void CaseRead_Sync_DifferentResultsEachCall() + { + Assert.NotSame( + Channel.CaseRead(Channel.Create(), i => { }), + Channel.CaseRead(Channel.Create(), i => { })); + } + + [Fact] + public void CaseRead_Async_DifferentResultsEachCall() + { + Assert.NotSame( + Channel.CaseRead(Channel.Create(), i => Task.CompletedTask), + Channel.CaseRead(Channel.Create(), i => Task.CompletedTask)); + } + + [Fact] + public void CaseWrite_Sync_DifferentResultsEachCall() + { + Assert.NotSame( + Channel.CaseWrite(Channel.Create(), 0, () => { }), + Channel.CaseWrite(Channel.Create(), 0, () => { })); + } + + [Fact] + public void CaseWrite_Async_DifferentResultsEachCall() + { + Assert.NotSame( + Channel.CaseWrite(Channel.Create(), 0, () => Task.CompletedTask), + Channel.CaseWrite(Channel.Create(), 0, () => Task.CompletedTask)); + } + + private sealed class CanReadFalseStream : MemoryStream + { + public override bool CanRead { get { return false; } } + } + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/DebuggerAttributes.cs b/src/System.Threading.Tasks.Channels/tests/DebuggerAttributes.cs new file mode 100644 index 00000000000..337940a1972 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/DebuggerAttributes.cs @@ -0,0 +1,140 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Collections.Generic; +using System.Linq; +using System.Reflection; + +namespace System.Diagnostics +{ + internal static class DebuggerAttributes + { + internal static object GetFieldValue(object obj, string fieldName) + { + return GetField(obj, fieldName).GetValue(obj); + } + + internal static void ValidateDebuggerTypeProxyProperties(object obj) + { + // Get the DebuggerTypeProxyAttibute for obj + var attrs = + obj.GetType().GetTypeInfo().CustomAttributes + .Where(a => a.AttributeType == typeof(DebuggerTypeProxyAttribute)) + .ToArray(); + if (attrs.Length != 1) + { + throw new InvalidOperationException( + string.Format("Expected one DebuggerTypeProxyAttribute on {0}.", obj)); + } + var cad = (CustomAttributeData)attrs[0]; + + // Get the proxy type. As written, this only works if the proxy and the target type + // have the same generic parameters, e.g. Dictionary and Proxy. + // It will not work with, for example, Dictionary.Keys and Proxy, + // as the former has two generic parameters and the latter only one. + Type proxyType = cad.ConstructorArguments[0].ArgumentType == typeof(Type) ? + (Type)cad.ConstructorArguments[0].Value : + Type.GetType((string)cad.ConstructorArguments[0].Value); + var genericArguments = obj.GetType().GenericTypeArguments; + if (genericArguments.Length > 0) + { + proxyType = proxyType.MakeGenericType(genericArguments); + } + + // Create an instance of the proxy type, and make sure we can access all of the instance properties + // on the type without exception + object proxyInstance = Activator.CreateInstance(proxyType, obj); + foreach (var pi in proxyInstance.GetType().GetTypeInfo().DeclaredProperties) + { + pi.GetValue(proxyInstance, null); + } + } + + internal static void ValidateDebuggerDisplayReferences(object obj) + { + // Get the DebuggerDisplayAttribute for obj + var attrs = + obj.GetType().GetTypeInfo().CustomAttributes + .Where(a => a.AttributeType == typeof(DebuggerDisplayAttribute)) + .ToArray(); + if (attrs.Length != 1) + { + throw new InvalidOperationException( + string.Format("Expected one DebuggerDisplayAttribute on {0}.", obj)); + } + var cad = (CustomAttributeData)attrs[0]; + + // Get the text of the DebuggerDisplayAttribute + string attrText = (string)cad.ConstructorArguments[0].Value; + + // Parse the text for all expressions + var references = new List(); + int pos = 0; + while (true) + { + int openBrace = attrText.IndexOf('{', pos); + if (openBrace < pos) break; + int closeBrace = attrText.IndexOf('}', openBrace); + if (closeBrace < openBrace) break; + + string reference = attrText.Substring(openBrace + 1, closeBrace - openBrace - 1).Replace(",nq", ""); + pos = closeBrace + 1; + + references.Add(reference); + } + if (references.Count == 0) + { + throw new InvalidOperationException( + string.Format("The DebuggerDisplayAttribute for {0} doesn't reference any expressions.", obj)); + } + + // Make sure that each referenced expression is a simple field or property name, and that we can + // invoke the property's get accessor or read from the field. + foreach (var reference in references) + { + PropertyInfo pi = GetProperty(obj, reference); + if (pi != null) + { + object ignored = pi.GetValue(obj, null); + continue; + } + + FieldInfo fi = GetField(obj, reference); + if (fi != null) + { + object ignored = fi.GetValue(obj); + continue; + } + + throw new InvalidOperationException( + string.Format("The DebuggerDisplayAttribute for {0} contains the expression \"{1}\".", obj, reference)); + } + } + + private static FieldInfo GetField(object obj, string fieldName) + { + for (Type t = obj.GetType(); t != null; t = t.GetTypeInfo().BaseType) + { + FieldInfo fi = t.GetTypeInfo().GetDeclaredField(fieldName); + if (fi != null) + { + return fi; + } + } + return null; + } + + private static PropertyInfo GetProperty(object obj, string propertyName) + { + for (Type t = obj.GetType(); t != null; t = t.GetTypeInfo().BaseType) + { + PropertyInfo pi = t.GetTypeInfo().GetDeclaredProperty(propertyName); + if (pi != null) + { + return pi; + } + } + return null; + } + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/EnumerableChannelTests.cs b/src/System.Threading.Tasks.Channels/tests/EnumerableChannelTests.cs new file mode 100644 index 00000000000..9eafc64280b --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/EnumerableChannelTests.cs @@ -0,0 +1,102 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Linq; +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public class EnumerableChannelTests : TestBase + { + [Fact] + public async Task Completion_NotEmpty_Idempotent() + { + IReadableChannel c = Channel.CreateFromEnumerable(Enumerable.Range(0, 10)); + Assert.Same(c.Completion, c.Completion); + while (await c.WaitToReadAsync()) + { + int result; + Assert.True(c.TryRead(out result)); + } + await c.Completion; + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + [InlineData(2)] + [InlineData(3)] + [InlineData(4)] + public async Task Range_AllDataReadable_Success(int mode) + { + const int Start = 42, Count = 99; + + IReadableChannel c = Channel.CreateFromEnumerable(Enumerable.Range(Start, Count)); + Assert.False(c.Completion.IsCompleted); + + int result; + for (int i = Start; i < Start + Count; i++) + { + switch (mode) + { + case 0: // TryRead + Assert.True(c.TryRead(out result)); + Assert.Equal(i, result); + break; + + case 1: // WaitToReadAsync then TryRead + Assert.True(await c.WaitToReadAsync()); + Assert.True(c.TryRead(out result)); + Assert.Equal(i, result); + break; + + case 2: // ReadAsync + Assert.Equal(i, await c.ReadAsync()); + break; + + case 3: // WaitToReadAsync then ReadAsync + Assert.True(await c.WaitToReadAsync()); + Assert.Equal(i, await c.ReadAsync()); + break; + + case 4: // Multiple WaitToReadAsync then ReadAsync + Assert.True(await c.WaitToReadAsync()); + Assert.True(await c.WaitToReadAsync()); + Assert.True(await c.WaitToReadAsync()); + Assert.True(await c.WaitToReadAsync()); + Assert.Equal(i, await c.ReadAsync()); + break; + } + } + + Assert.False(await c.WaitToReadAsync()); + Assert.False(await c.WaitToReadAsync()); + + Assert.False(c.TryRead(out result)); + Assert.False(c.TryRead(out result)); + + await Assert.ThrowsAnyAsync(() => c.ReadAsync().AsTask()); + await Assert.ThrowsAnyAsync(() => c.ReadAsync().AsTask()); + + await c.Completion; + } + + [Fact] + public async Task ReadAsync_NoMoreDataAvailable_ThrowsException() + { + IReadableChannel c = Channel.CreateFromEnumerable(Enumerable.Empty()); + await Assert.ThrowsAnyAsync(() => c.ReadAsync().AsTask()); + } + + [Fact] + public void Precancellation_Reading_ReturnsCanceledImmediately() + { + IReadableChannel c = Channel.CreateFromEnumerable(Enumerable.Empty()); + var cts = new CancellationTokenSource(); + cts.Cancel(); + + AssertSynchronouslyCanceled(c.ReadAsync(cts.Token).AsTask(), cts.Token); + AssertSynchronouslyCanceled(c.WaitToReadAsync(cts.Token), cts.Token); + } + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/ObservableObserverTests.cs b/src/System.Threading.Tasks.Channels/tests/ObservableObserverTests.cs new file mode 100644 index 00000000000..f2e2910081d --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/ObservableObserverTests.cs @@ -0,0 +1,47 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public class ObservableObserverTests : TestBase + { + [Fact] + public void AsObservable_InvalidSubscribe_ThrowsException() + { + IChannel c = Channel.Create(); + IObservable o = c.AsObservable(); + Assert.Throws("observer", () => o.Subscribe(null)); + } + + [Fact] + public void AsObservable_Subscribe_Dispose_Success() + { + IChannel c = Channel.Create(); + IObservable o = c.AsObservable(); + + using (o.Subscribe(new DelegateObserver())) + { + } + + using (o.Subscribe(new DelegateObserver())) + using (o.Subscribe(new DelegateObserver())) + using (o.Subscribe(new DelegateObserver())) + { + } + } + + [Fact] + public void AsObservable_Subscribe_DisposeMultipleTimes_Success() + { + IChannel c = Channel.Create(); + IObservable o = c.AsObservable(); + + IDisposable d = o.Subscribe(new DelegateObserver()); + d.Dispose(); + d.Dispose(); + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/SerializationChannelTests.cs b/src/System.Threading.Tasks.Channels/tests/SerializationChannelTests.cs new file mode 100644 index 00000000000..50c1aac37b6 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/SerializationChannelTests.cs @@ -0,0 +1,166 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.IO; +using System.IO.Pipes; +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public class SerializationChannelTests : TestBase + { + [Theory] + [InlineData(false)] + [InlineData(true)] + public async Task Pipes_ReadWriteValues(bool firstWaitToRead) + { + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (byte)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (sbyte)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (uint)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (int)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (ushort)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (short)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (ulong)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (long)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (float)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (double)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (char)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => (decimal)i); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => i % 2 == 0); + await Pipes_ReadWriteValues(firstWaitToRead, 10,i => i.ToString()); + await Assert.ThrowsAnyAsync(() => Pipes_ReadWriteValues(firstWaitToRead, 10, i => DateTime.UtcNow)); + } + + private static async Task Pipes_ReadWriteValues(bool firstWaitToRead, int numItems, Func getValue) + { + using (AnonymousPipeServerStream serverPipe = new AnonymousPipeServerStream(PipeDirection.Out)) + using (AnonymousPipeClientStream clientPipe = new AnonymousPipeClientStream(PipeDirection.In, serverPipe.ClientSafePipeHandle)) + { + IWriteableChannel writer = Channel.WriteToStream(serverPipe); + IReadableChannel reader = Channel.ReadFromStream(clientPipe); + + for (int i = 0; i < numItems; i++) + { + T itemToWrite = getValue(i); + + Task readItem = firstWaitToRead ? + reader.WaitToReadAsync().ContinueWith(_ => reader.ReadAsync().AsTask()).Unwrap() : + reader.ReadAsync().AsTask(); + Task writeItem = writer.WriteAsync(itemToWrite); + await Task.WhenAll(readItem, writeItem); + + Assert.Equal(itemToWrite, readItem.Result); + } + + writer.Complete(); + Assert.False(await reader.WaitToReadAsync()); + await reader.Completion; + } + } + + [Fact] + public void WaitToWriteAsync_Completed_SynchronousResult() + { + using (AnonymousPipeServerStream serverPipe = new AnonymousPipeServerStream(PipeDirection.Out)) + { + IWriteableChannel writer = Channel.WriteToStream(serverPipe); + AssertSynchronousTrue(writer.WaitToWriteAsync()); + + writer.Complete(); + AssertSynchronousFalse(writer.WaitToWriteAsync()); + + var cts = new CancellationTokenSource(); + cts.Cancel(); + AssertSynchronouslyCanceled(writer.WaitToWriteAsync(cts.Token), cts.Token); + } + } + + [Fact] + public void WriteToStream_Precancellation() + { + IWriteableChannel w = Channel.WriteToStream(new MemoryStream()); + var cts = new CancellationTokenSource(); + cts.Cancel(); + AssertSynchronouslyCanceled(w.WaitToWriteAsync(cts.Token), cts.Token); + AssertSynchronouslyCanceled(w.WriteAsync(42, cts.Token), cts.Token); + } + + [Fact] + public void ReadFromStream_Precancellation() + { + IReadableChannel r = Channel.ReadFromStream(new MemoryStream()); + var cts = new CancellationTokenSource(); + cts.Cancel(); + AssertSynchronouslyCanceled(r.WaitToReadAsync(cts.Token), cts.Token); + AssertSynchronouslyCanceled(r.ReadAsync(cts.Token).AsTask(), cts.Token); + } + + [Fact] + private static void Pipes_EnumerateValues() + { + using (AnonymousPipeServerStream serverPipe = new AnonymousPipeServerStream(PipeDirection.Out)) + using (AnonymousPipeClientStream clientPipe = new AnonymousPipeClientStream(PipeDirection.In, serverPipe.ClientSafePipeHandle)) + { + IWriteableChannel writer = Channel.WriteToStream(serverPipe); + IReadableChannel reader = Channel.ReadFromStream(clientPipe); + + Task.WaitAll( + Task.Run(async () => + { + for (int i = 0; i < 100; i++) + { + await writer.WriteAsync(i); + } + writer.Complete(); + Assert.False(writer.TryWrite(100)); + }), + Task.Run(async () => + { + int i = 0; + IAsyncEnumerator e = reader.GetAsyncEnumerator(); + while (await e.MoveNextAsync()) + { + Assert.Equal(i++, e.Current); + } + })); + } + } + + [Fact] + private static void Pipes_WaitForReadThenTryReadValues() + { + using (AnonymousPipeServerStream serverPipe = new AnonymousPipeServerStream(PipeDirection.Out)) + using (AnonymousPipeClientStream clientPipe = new AnonymousPipeClientStream(PipeDirection.In, serverPipe.ClientSafePipeHandle)) + { + IWriteableChannel writer = Channel.WriteToStream(serverPipe); + IReadableChannel reader = Channel.ReadFromStream(clientPipe); + + Task.WaitAll( + Task.Run(async () => + { + for (int i = 0; i < 100; i++) + { + await writer.WriteAsync(i); + } + writer.Complete(); + Assert.False(writer.TryWrite(100)); + }), + Task.Run(async () => + { + int result; + int i = 0; + while (await reader.WaitToReadAsync()) + { + if (reader.TryRead(out result)) + { + Assert.Equal(i++, result); + } + } + Assert.False(reader.TryRead(out result)); + })); + } + } + + + } +} \ No newline at end of file diff --git a/src/System.Threading.Tasks.Channels/tests/System.Threading.Tasks.Channels.Tests.csproj b/src/System.Threading.Tasks.Channels/tests/System.Threading.Tasks.Channels.Tests.csproj new file mode 100644 index 00000000000..f366de8e66a --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/System.Threading.Tasks.Channels.Tests.csproj @@ -0,0 +1,39 @@ + + + + + Debug + AnyCPU + System.Threading.Tasks.Channels.Tests + Library + System.Threading.Tasks.Channels.Tests + true + v5.0 + + .NETPlatform,Version=v5.0 + {DD942FDB-184A-4D9F-8A1E-3D531858A4B1} + + + + + + + + + + + + + + + + + + System.Threading.Tasks.Channels + + + + + + + \ No newline at end of file diff --git a/src/System.Threading.Tasks.Channels/tests/TestBase.cs b/src/System.Threading.Tasks.Channels/tests/TestBase.cs new file mode 100644 index 00000000000..6ed48a60378 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/TestBase.cs @@ -0,0 +1,60 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public abstract class TestBase + { + protected void AssertSynchronouslyCanceled(Task task, CancellationToken token) + { + Assert.True(task.IsCanceled); + OperationCanceledException oce = Assert.ThrowsAny(() => task.GetAwaiter().GetResult()); + Assert.Equal(token, oce.CancellationToken); + } + + protected async Task AssertCanceled(Task task, CancellationToken token) + { + await Assert.ThrowsAnyAsync(() => task); + AssertSynchronouslyCanceled(task, token); + } + + protected void AssertSynchronousTrue(Task task) + { + Assert.Equal(TaskStatus.RanToCompletion, task.Status); + Assert.True(task.Result); + } + + protected void AssertSynchronousFalse(Task task) + { + Assert.Equal(TaskStatus.RanToCompletion, task.Status); + Assert.False(task.Result); + } + + internal sealed class DelegateObserver : IObserver + { + public Action OnNextDelegate = null; + public Action OnErrorDelegate = null; + public Action OnCompletedDelegate = null; + + void IObserver.OnNext(T value) + { + if (OnNextDelegate != null) + OnNextDelegate(value); + } + + void IObserver.OnError(Exception error) + { + if (OnErrorDelegate != null) + OnErrorDelegate(error); + } + + void IObserver.OnCompleted() + { + if (OnCompletedDelegate != null) + OnCompletedDelegate(); + } + } + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/UnboundedChannelTests.cs b/src/System.Threading.Tasks.Channels/tests/UnboundedChannelTests.cs new file mode 100644 index 00000000000..d2dbc12faad --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/UnboundedChannelTests.cs @@ -0,0 +1,165 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public class SingleReaderWriterUnboundedChannelTests : UnboundedChannelTests + { + protected override IChannel CreateChannel() + { + return Channel.Create(singleReaderWriter: true); + } + + protected override bool RequiresSingleReaderWriter { get { return true; } } + + [Fact] + public void ValidateInternalDebuggerAttributes() + { + IChannel c = CreateChannel(); + Assert.True(c.TryWrite(1)); + Assert.True(c.TryWrite(2)); + + var queue = DebuggerAttributes.GetFieldValue(c, "_items"); + DebuggerAttributes.ValidateDebuggerDisplayReferences(queue); + DebuggerAttributes.ValidateDebuggerTypeProxyProperties(queue); + } + + [Fact] + public async Task MultipleWaiters_ThrowsInvalid() + { + IChannel c = CreateChannel(); + Task t1 = c.WaitToReadAsync(); + Task t2 = c.WaitToReadAsync(); + await Assert.ThrowsAsync(() => t1); + } + + [Fact] + public void Stress_TryWrite_TryRead() + { + const int NumItems = 3000000; + IChannel c = CreateChannel(); + + Task.WaitAll( + Task.Run(async () => + { + int received = 0; + while (await c.WaitToReadAsync()) + { + int i; + while (c.TryRead(out i)) + { + Assert.Equal(received, i); + received++; + } + } + }), + Task.Run(() => + { + for (int i = 0; i < NumItems; i++) + { + Assert.True(c.TryWrite(i)); + } + c.Complete(); + })); + } + } + + public class MultiReaderWriterUnboundedChannelTests : UnboundedChannelTests + { + protected override IChannel CreateChannel() + { + return Channel.Create(singleReaderWriter: false); + } + } + + public abstract class UnboundedChannelTests : ChannelTestBase + { + [Fact] + public async Task Complete_BeforeEmpty_NoWaiters_TriggersCompletion() + { + IChannel c = CreateChannel(); + Assert.True(c.TryWrite(42)); + c.Complete(); + Assert.False(c.Completion.IsCompleted); + Assert.Equal(42, await c.ReadAsync()); + await c.Completion; + } + + [Fact] + public void TryWrite_TryRead_Many() + { + IChannel c = CreateChannel(); + + const int NumItems = 100000; + for (int i = 0; i < NumItems; i++) + { + Assert.True(c.TryWrite(i)); + } + for (int i = 0; i < NumItems; i++) + { + int result; + Assert.True(c.TryRead(out result)); + Assert.Equal(i, result); + } + } + + [Fact] + public void TryWrite_TryRead_OneAtATime() + { + IChannel c = CreateChannel(); + + for (int i = 0; i < 10; i++) + { + Assert.True(c.TryWrite(i)); + int result; + Assert.True(c.TryRead(out result)); + Assert.Equal(i, result); + } + } + + [Fact] + public void WaitForReadAsync_DataAvailable_CompletesSynchronously() + { + IChannel c = CreateChannel(); + Assert.True(c.TryWrite(42)); + AssertSynchronousTrue(c.WaitToReadAsync()); + } + + [Theory] + [InlineData(0)] + [InlineData(1)] + public async Task WriteMany_ThenComplete_SuccessfullyReadAll(int readMode) + { + IChannel c = CreateChannel(); + for (int i = 0; i < 10; i++) + { + Assert.True(c.TryWrite(i)); + } + + c.Complete(); + Assert.False(c.Completion.IsCompleted); + + for (int i = 0; i < 10; i++) + { + Assert.False(c.Completion.IsCompleted); + switch (readMode) + { + case 0: + int result; + Assert.True(c.TryRead(out result)); + Assert.Equal(i, result); + break; + case 1: + Assert.Equal(i, await c.ReadAsync()); + break; + } + } + + await c.Completion; + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/UnbufferedChannelTests.cs b/src/System.Threading.Tasks.Channels/tests/UnbufferedChannelTests.cs new file mode 100644 index 00000000000..c00c04243b3 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/UnbufferedChannelTests.cs @@ -0,0 +1,118 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public class UnbufferedChannelTests : ChannelTestBase + { + protected override IChannel CreateChannel() + { + return Channel.CreateUnbuffered(); + } + + [Fact] + public async Task Complete_BeforeEmpty_WaitingWriters_TriggersCompletion() + { + IChannel c = Channel.CreateUnbuffered(); + Task write1 = c.WriteAsync(42); + Task write2 = c.WriteAsync(43); + c.Complete(); + await c.Completion; + await Assert.ThrowsAnyAsync(() => write1); + await Assert.ThrowsAnyAsync(() => write2); + } + + [Fact] + public void TryReadWrite_NoPartner_Fail() + { + IChannel c = Channel.CreateUnbuffered(); + Assert.False(c.TryWrite(42)); + int result; + Assert.False(c.TryRead(out result)); + Assert.Equal(result, 0); + } + + [Fact] + public void ReadAsync_TryWrite_Success() + { + IChannel c = Channel.CreateUnbuffered(); + ValueTask r = c.ReadAsync(); + Assert.False(r.IsRanToCompletion); + Assert.False(r.AsTask().IsCompleted); + Assert.True(c.TryWrite(42)); + Assert.Equal(42, r.Result); + } + + [Fact] + public void TryRead_WriteAsync_Success() + { + IChannel c = Channel.CreateUnbuffered(); + Task w = c.WriteAsync(42); + Assert.False(w.IsCompleted); + int result; + Assert.True(c.TryRead(out result)); + Assert.Equal(42, result); + } + + [Fact] + public async Task Cancel_UnpartneredWrite_ThrowsCancellationException() + { + IChannel c = Channel.CreateUnbuffered(); + var cts = new CancellationTokenSource(); + + Task w = c.WriteAsync(42, cts.Token); + Assert.False(w.IsCompleted); + + cts.Cancel(); + await AssertCanceled(w, cts.Token); + } + + [Fact] + public async Task Cancel_UnpartneredRead_ThrowsCancellationException() + { + IChannel c = Channel.CreateUnbuffered(); + var cts = new CancellationTokenSource(); + + Task r = c.ReadAsync(cts.Token).AsTask(); + Assert.False(r.IsCompleted); + + cts.Cancel(); + await AssertCanceled(r, cts.Token); + } + + [Fact] + public async Task Cancel_PartneredWrite_Success() + { + IChannel c = Channel.CreateUnbuffered(); + var cts = new CancellationTokenSource(); + + Task w = c.WriteAsync(42, cts.Token); + Assert.False(w.IsCompleted); + + ValueTask r = c.ReadAsync(); + Assert.True(r.IsRanToCompletion); + + cts.Cancel(); + await w; // no throw + } + + [Fact] + public async Task Cancel_PartneredRead_Success() + { + IChannel c = Channel.CreateUnbuffered(); + var cts = new CancellationTokenSource(); + + ValueTask r = c.ReadAsync(cts.Token); + Assert.False(r.IsRanToCompletion); + + Task w = c.WriteAsync(42); + Assert.True(w.IsCompleted); + + cts.Cancel(); + Assert.Equal(42, await r); + } + + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/ValueTaskTests.cs b/src/System.Threading.Tasks.Channels/tests/ValueTaskTests.cs new file mode 100644 index 00000000000..547a4a33456 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/ValueTaskTests.cs @@ -0,0 +1,127 @@ +// Copyright (c) Microsoft. All rights reserved. +// Licensed under the MIT license. See LICENSE file in the project root for full license information. + +using System.Diagnostics; +using Xunit; + +namespace System.Threading.Tasks.Channels.Tests +{ + public class ValueTaskTests + { + [Fact] + public void ValidateDebuggerAttributes() + { + DebuggerAttributes.ValidateDebuggerDisplayReferences(new ValueTask(42)); + DebuggerAttributes.ValidateDebuggerDisplayReferences(new ValueTask(Task.FromResult("42"))); + DebuggerAttributes.ValidateDebuggerDisplayReferences(new ValueTask(new TaskCompletionSource().Task)); + } + + [Fact] + public void DefaultValueTask_ValueType_DefaultValue() + { + Assert.True(default(ValueTask).IsRanToCompletion); + Assert.Equal(0, default(ValueTask).Result); + + Assert.True(default(ValueTask).IsRanToCompletion); + Assert.Equal(null, default(ValueTask).Result); + } + + [Fact] + public void CreateFromValue_IsRanToCompletion() + { + ValueTask t = new ValueTask(42); + Assert.True(t.IsRanToCompletion); + Assert.Equal(42, t.Result); + } + + [Fact] + public void CreateFromCompletedTask_IsRanToCompletion() + { + ValueTask t = new ValueTask(Task.FromResult(42)); + Assert.True(t.IsRanToCompletion); + Assert.Equal(42, t.Result); + } + + [Fact] + public void CreateFromNotCompletedTask_IsNotRanToCompletion() + { + var tcs = new TaskCompletionSource(); + ValueTask t = new ValueTask(tcs.Task); + Assert.False(t.IsRanToCompletion); + tcs.SetResult(42); + Assert.Equal(42, t.Result); + } + + [Fact] + public void CastFromValue_IsRanToCompletion() + { + ValueTask t = 42; + Assert.True(t.IsRanToCompletion); + Assert.Equal(42, t.Result); + } + + [Fact] + public void CastFromCompletedTask_IsRanToCompletion() + { + ValueTask t = Task.FromResult(42); + Assert.True(t.IsRanToCompletion); + Assert.Equal(42, t.Result); + } + + [Fact] + public void CastFromFaultedTask_IsNotRanToCompletion() + { + ValueTask t = Task.FromException(new FormatException()); + Assert.False(t.IsRanToCompletion); + Assert.Throws(() => t.Result); + } + + [Fact] + public void CreateFromTask_AsTaskIdemptotent() + { + Task source = Task.FromResult(42); + ValueTask t = new ValueTask(source); + Assert.Same(source, t.AsTask()); + Assert.Same(t.AsTask(), t.AsTask()); + } + + [Fact] + public void CreateFromValue_AsTaskNotIdemptotent() + { + ValueTask t = new ValueTask(42); + Assert.NotSame(t.AsTask(), t.AsTask()); + } + + [Fact] + public async Task CreateFromValue_Await() + { + ValueTask t = new ValueTask(42); + Assert.Equal(42, await t); + Assert.Equal(42, await t.ConfigureAwait(false)); + Assert.Equal(42, await t.ConfigureAwait(true)); + } + + [Fact] + public async Task CreateFromTask_Await() + { + Task source = Task.Delay(1).ContinueWith(_ => 42); + ValueTask t = new ValueTask(source); + Assert.Equal(42, await t); + Assert.Equal(42, await t.ConfigureAwait(false)); + Assert.Equal(42, await t.ConfigureAwait(true)); + } + + [Fact] + public void Awaiter_OnCompleted() + { + // Since ValueTask implementes both OnCompleted and UnsafeOnCompleted, + // OnCompleted typically won't be used by await, so we add an explicit test + // for it here. + + ValueTask t = 42; + var mres = new ManualResetEventSlim(); + t.GetAwaiter().OnCompleted(() => mres.Set()); + Assert.True(mres.Wait(10000)); + } + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/project.json b/src/System.Threading.Tasks.Channels/tests/project.json new file mode 100644 index 00000000000..3c092df1831 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/project.json @@ -0,0 +1,26 @@ +{ + "dependencies": { + "System.Collections": "4.0.10-*", + "System.Console": "4.0.0-*", + "System.Diagnostics.Debug": "4.0.10-*", + "System.Diagnostics.Tools": "4.0.1-beta-*", + "System.IO": "4.0.10", + "System.IO.FileSystem": "4.0.0", + "System.IO.Pipes": "4.0.0-beta-*", + "System.Linq": "4.0.0-*", + "System.Reflection.Primitives": "4.0.0", + "System.Resources.ResourceManager": "4.0.0-*", + "System.Runtime": "4.0.20", + "System.Runtime.Extensions": "4.0.10", + "System.Runtime.InteropServices": "4.0.20-*", + "System.Threading": "4.0.10", + "xunit": "2.0.0-beta5-build2785", + "Microsoft.NETCore.Targets": "1.0.1-beta-*" + }, + "frameworks": { + "dnxcore50": { } + }, + "runtimes": { + "win7-x86": {} + } +} diff --git a/src/System.Threading.Tasks.Channels/tests/project.lock.json b/src/System.Threading.Tasks.Channels/tests/project.lock.json new file mode 100644 index 00000000000..528b031e970 --- /dev/null +++ b/src/System.Threading.Tasks.Channels/tests/project.lock.json @@ -0,0 +1,2210 @@ +{ + "locked": false, + "version": -9996, + "targets": { + "DNXCore,Version=v5.0": { + "Microsoft.NETCore.Platforms/1.0.1-beta-23413": {}, + "Microsoft.NETCore.Targets/1.0.1-beta-23413": { + "dependencies": { + "Microsoft.NETCore.Targets.DNXCore": "5.0.0-beta-23413", + "Microsoft.NETCore.Platforms": "1.0.1-beta-23413" + } + }, + "Microsoft.NETCore.Targets.DNXCore/5.0.0-beta-23413": {}, + "System.Collections/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.20" + }, + "compile": { + "ref/dotnet/System.Collections.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Collections.dll": {} + } + }, + "System.Console/4.0.0-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.IO": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Console.dll": {} + } + }, + "System.Diagnostics.Debug/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Diagnostics.Debug.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Diagnostics.Debug.dll": {} + } + }, + "System.Diagnostics.Tools/4.0.1-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Diagnostics.Tools.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Diagnostics.Tools.dll": {} + } + }, + "System.Globalization/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Globalization.dll": {} + } + }, + "System.IO/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.20", + "System.Text.Encoding": "4.0.0", + "System.Threading.Tasks": "4.0.0" + }, + "compile": { + "ref/dotnet/System.IO.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.IO.dll": {} + } + }, + "System.IO.FileSystem/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.20", + "System.Runtime.InteropServices": "4.0.20", + "System.Resources.ResourceManager": "4.0.0", + "System.IO.FileSystem.Primitives": "4.0.0", + "System.Runtime.Handles": "4.0.0", + "System.Threading.Overlapped": "4.0.0", + "System.Text.Encoding": "4.0.10", + "System.IO": "4.0.10", + "System.Collections": "4.0.10", + "System.Threading.Tasks": "4.0.10", + "System.Runtime.Extensions": "4.0.10", + "System.Text.Encoding.Extensions": "4.0.10", + "System.Threading": "4.0.10" + }, + "compile": { + "ref/dotnet/System.IO.FileSystem.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.IO.FileSystem.dll": {} + } + }, + "System.IO.FileSystem.Primitives/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.20" + }, + "compile": { + "ref/dotnet/System.IO.FileSystem.Primitives.dll": {} + }, + "runtime": { + "lib/dotnet/System.IO.FileSystem.Primitives.dll": {} + } + }, + "System.IO.Pipes/4.0.0-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Runtime.Handles": "4.0.0", + "System.Security.Principal": "4.0.0", + "System.Threading.Tasks": "4.0.0", + "System.IO": "4.0.0" + }, + "compile": { + "ref/dotnet/System.IO.Pipes.dll": {} + } + }, + "System.Linq/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.20", + "System.Collections": "4.0.10", + "System.Resources.ResourceManager": "4.0.0", + "System.Diagnostics.Debug": "4.0.10", + "System.Runtime.Extensions": "4.0.10" + }, + "compile": { + "ref/dotnet/System.Linq.dll": {} + }, + "runtime": { + "lib/dotnet/System.Linq.dll": {} + } + }, + "System.Private.Uri/4.0.0": { + "runtime": { + "lib/DNXCore50/System.Private.Uri.dll": {} + } + }, + "System.Reflection/4.0.0": { + "dependencies": { + "System.IO": "4.0.0", + "System.Reflection.Primitives": "4.0.0", + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Reflection.dll": {} + } + }, + "System.Reflection.Primitives/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Reflection.Primitives.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Reflection.Primitives.dll": {} + } + }, + "System.Resources.ResourceManager/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Reflection": "4.0.0", + "System.Globalization": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Resources.ResourceManager.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Resources.ResourceManager.dll": {} + } + }, + "System.Runtime/4.0.20": { + "dependencies": { + "System.Private.Uri": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Runtime.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Runtime.dll": {} + } + }, + "System.Runtime.Extensions/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.20" + }, + "compile": { + "ref/dotnet/System.Runtime.Extensions.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Runtime.Extensions.dll": {} + } + }, + "System.Runtime.Handles/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Runtime.Handles.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Runtime.Handles.dll": {} + } + }, + "System.Runtime.InteropServices/4.0.20": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Reflection": "4.0.0", + "System.Reflection.Primitives": "4.0.0", + "System.Runtime.Handles": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Runtime.InteropServices.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Runtime.InteropServices.dll": {} + } + }, + "System.Security.Principal/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Security.Principal.dll": {} + }, + "runtime": { + "lib/dotnet/System.Security.Principal.dll": {} + } + }, + "System.Text.Encoding/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Text.Encoding.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Text.Encoding.dll": {} + } + }, + "System.Text.Encoding.Extensions/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Text.Encoding": "4.0.10" + }, + "compile": { + "ref/dotnet/System.Text.Encoding.Extensions.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Text.Encoding.Extensions.dll": {} + } + }, + "System.Threading/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Threading.Tasks": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Threading.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Threading.dll": {} + } + }, + "System.Threading.Overlapped/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Runtime.Handles": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Threading.Overlapped.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Threading.Overlapped.dll": {} + } + }, + "System.Threading.Tasks/4.0.10": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Threading.Tasks.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Threading.Tasks.dll": {} + } + }, + "xunit/2.0.0-beta5-build2785": { + "dependencies": { + "xunit.core": "[2.0.0-beta5-build2785]", + "xunit.assert": "[2.0.0-beta5-build2785]" + } + }, + "xunit.abstractions/2.0.0-beta5-build2785": { + "compile": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.abstractions.dll": {} + }, + "runtime": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.abstractions.dll": {} + } + }, + "xunit.assert/2.0.0-beta5-build2785": { + "compile": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monoandroid+monotouch10/xunit.assert.dll": {} + }, + "runtime": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monoandroid+monotouch10/xunit.assert.dll": {} + } + }, + "xunit.core/2.0.0-beta5-build2785": { + "dependencies": { + "xunit.abstractions": "[2.0.0-beta5-build2785]" + }, + "compile": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.core.dll": {} + }, + "runtime": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.core.dll": {} + } + } + }, + "DNXCore,Version=v5.0/win7-x86": { + "Microsoft.NETCore.Platforms/1.0.1-beta-23413": {}, + "Microsoft.NETCore.Targets/1.0.1-beta-23413": { + "dependencies": { + "Microsoft.NETCore.Targets.DNXCore": "5.0.0-beta-23413", + "Microsoft.NETCore.Platforms": "1.0.1-beta-23413" + } + }, + "Microsoft.NETCore.Targets.DNXCore/5.0.0-beta-23413": {}, + "runtime.win7.System.Console/4.0.0-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.20", + "System.Runtime.InteropServices": "4.0.20", + "System.Resources.ResourceManager": "4.0.0", + "System.IO.FileSystem.Primitives": "4.0.0", + "System.IO": "4.0.10", + "System.Threading.Tasks": "4.0.10", + "System.Text.Encoding": "4.0.10", + "System.Threading": "4.0.10", + "System.Text.Encoding.Extensions": "4.0.10" + }, + "runtime": { + "runtimes/win7/lib/dotnet/System.Console.dll": {} + } + }, + "runtime.win7.System.IO.Pipes/4.0.0-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.20", + "System.Runtime.InteropServices": "4.0.20", + "System.Resources.ResourceManager": "4.0.0", + "System.Runtime.Handles": "4.0.0", + "System.Threading.Overlapped": "4.0.0", + "System.IO.FileSystem.Primitives": "4.0.0", + "System.Security.Principal": "4.0.0", + "System.Threading.Tasks": "4.0.10", + "System.Threading": "4.0.10", + "System.IO": "4.0.10", + "System.Runtime.Extensions": "4.0.10" + }, + "runtime": { + "runtimes/win7/lib/dotnet/System.IO.Pipes.dll": {} + } + }, + "System.Collections/4.0.11-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.21-beta-23413" + }, + "compile": { + "ref/dotnet/System.Collections.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Collections.dll": {} + } + }, + "System.Console/4.0.0-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.IO": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Console.dll": {} + } + }, + "System.Diagnostics.Debug/4.0.11-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Diagnostics.Debug.dll": {} + } + }, + "System.Diagnostics.Tools/4.0.1-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Diagnostics.Tools.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Diagnostics.Tools.dll": {} + } + }, + "System.Globalization/4.0.11-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Globalization.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Globalization.dll": {} + } + }, + "System.IO/4.0.11-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.20", + "System.Text.Encoding": "4.0.0", + "System.Threading.Tasks": "4.0.0" + }, + "compile": { + "ref/dotnet/System.IO.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.IO.dll": {} + } + }, + "System.IO.FileSystem/4.0.1-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Text.Encoding": "4.0.0", + "System.IO": "4.0.0", + "System.IO.FileSystem.Primitives": "4.0.0", + "System.Threading.Tasks": "4.0.0", + "System.Runtime.Handles": "4.0.0" + }, + "compile": { + "ref/dotnet/System.IO.FileSystem.dll": {} + } + }, + "System.IO.FileSystem.Primitives/4.0.1-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.20" + }, + "compile": { + "ref/dotnet/System.IO.FileSystem.Primitives.dll": {} + }, + "runtime": { + "lib/dotnet/System.IO.FileSystem.Primitives.dll": {} + } + }, + "System.IO.Pipes/4.0.0-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Runtime.Handles": "4.0.0", + "System.Security.Principal": "4.0.0", + "System.Threading.Tasks": "4.0.0", + "System.IO": "4.0.0" + }, + "compile": { + "ref/dotnet/System.IO.Pipes.dll": {} + } + }, + "System.Linq/4.0.1-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.20", + "System.Collections": "4.0.10", + "System.Resources.ResourceManager": "4.0.0", + "System.Diagnostics.Debug": "4.0.10", + "System.Runtime.Extensions": "4.0.10" + }, + "compile": { + "ref/dotnet/System.Linq.dll": {} + }, + "runtime": { + "lib/dotnet/System.Linq.dll": {} + } + }, + "System.Private.Uri/4.0.1-beta-23413": {}, + "System.Reflection/4.1.0-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.20", + "System.IO": "4.0.0", + "System.Reflection.Primitives": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Reflection.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Reflection.dll": {} + } + }, + "System.Reflection.Primitives/4.0.1-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Reflection.Primitives.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Reflection.Primitives.dll": {} + } + }, + "System.Resources.ResourceManager/4.0.1-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Reflection": "4.0.0", + "System.Globalization": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Resources.ResourceManager.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Resources.ResourceManager.dll": {} + } + }, + "System.Runtime/4.0.21-beta-23413": { + "dependencies": { + "System.Private.Uri": "4.0.1-beta-23413" + }, + "compile": { + "ref/dotnet/System.Runtime.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Runtime.dll": {} + } + }, + "System.Runtime.Extensions/4.0.11-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.20" + }, + "compile": { + "ref/dotnet/System.Runtime.Extensions.dll": {} + } + }, + "System.Runtime.Handles/4.0.1-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Runtime.Handles.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Runtime.Handles.dll": {} + } + }, + "System.Runtime.InteropServices/4.0.21-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Reflection": "4.0.0", + "System.Reflection.Primitives": "4.0.0", + "System.Runtime.Handles": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Runtime.InteropServices.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Runtime.InteropServices.dll": {} + } + }, + "System.Security.Principal/4.0.1-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Security.Principal.dll": {} + }, + "runtime": { + "lib/dotnet/System.Security.Principal.dll": {} + } + }, + "System.Text.Encoding/4.0.11-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Text.Encoding.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Text.Encoding.dll": {} + } + }, + "System.Text.Encoding.Extensions/4.0.11-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Text.Encoding": "4.0.10" + }, + "compile": { + "ref/dotnet/System.Text.Encoding.Extensions.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Text.Encoding.Extensions.dll": {} + } + }, + "System.Threading/4.0.11-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Threading.Tasks": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Threading.dll": {} + } + }, + "System.Threading.Overlapped/4.0.0": { + "dependencies": { + "System.Runtime": "4.0.0", + "System.Runtime.Handles": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Threading.Overlapped.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Threading.Overlapped.dll": {} + } + }, + "System.Threading.Tasks/4.0.11-beta-23413": { + "dependencies": { + "System.Runtime": "4.0.0" + }, + "compile": { + "ref/dotnet/System.Threading.Tasks.dll": {} + }, + "runtime": { + "lib/DNXCore50/System.Threading.Tasks.dll": {} + } + }, + "xunit/2.0.0-beta5-build2785": { + "dependencies": { + "xunit.core": "[2.0.0-beta5-build2785]", + "xunit.assert": "[2.0.0-beta5-build2785]" + } + }, + "xunit.abstractions/2.0.0-beta5-build2785": { + "compile": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.abstractions.dll": {} + }, + "runtime": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.abstractions.dll": {} + } + }, + "xunit.assert/2.0.0-beta5-build2785": { + "compile": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monoandroid+monotouch10/xunit.assert.dll": {} + }, + "runtime": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monoandroid+monotouch10/xunit.assert.dll": {} + } + }, + "xunit.core/2.0.0-beta5-build2785": { + "dependencies": { + "xunit.abstractions": "[2.0.0-beta5-build2785]" + }, + "compile": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.core.dll": {} + }, + "runtime": { + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.core.dll": {} + } + } + } + }, + "libraries": { + "Microsoft.NETCore.Platforms/1.0.1-beta-23413": { + "sha512": "aCvLDo6TqWDIKCGpiZnmSqwnvgFcF2HE7o12ft5uMpJ3LUhrqk0nSM38zhNUKcf8SPMaLk3k2ft7uHDy9Z3X/g==", + "files": [ + "Microsoft.NETCore.Platforms.1.0.1-beta-23413.nupkg", + "Microsoft.NETCore.Platforms.1.0.1-beta-23413.nupkg.sha512", + "Microsoft.NETCore.Platforms.nuspec", + "runtime.json" + ] + }, + "Microsoft.NETCore.Targets/1.0.1-beta-23413": { + "sha512": "99N/Bg08OJishH4GUyKKvewdDlBmOG75YQ7UotySPXXxG4SMAsPTK05sQt8u3CIa8bt69HmpCcPpSdD5+YSUAA==", + "files": [ + "Microsoft.NETCore.Targets.1.0.1-beta-23413.nupkg", + "Microsoft.NETCore.Targets.1.0.1-beta-23413.nupkg.sha512", + "Microsoft.NETCore.Targets.nuspec", + "runtime.json" + ] + }, + "Microsoft.NETCore.Targets.DNXCore/5.0.0-beta-23413": { + "sha512": "a4/LhYmIP2W78RIe7gas1AZQIvGDTRqsJvAw1rbCjledslXMwUx7cn/TKnlcjr0wBs3D/OwrzqaBx79D8dA3tg==", + "files": [ + "Microsoft.NETCore.Targets.DNXCore.5.0.0-beta-23413.nupkg", + "Microsoft.NETCore.Targets.DNXCore.5.0.0-beta-23413.nupkg.sha512", + "Microsoft.NETCore.Targets.DNXCore.nuspec", + "runtime.json" + ] + }, + "runtime.win7.System.Console/4.0.0-beta-23413": { + "serviceable": true, + "sha512": "aVW6EkOO6tFKEyqCg2DfmKWasd8gE0Vhm0YlBZqiNC4GTh1OqUkV5KHaZDF51mxbFcdovsMFmQ3SrPczQA0pwQ==", + "files": [ + "runtime.win7.System.Console.4.0.0-beta-23413.nupkg", + "runtime.win7.System.Console.4.0.0-beta-23413.nupkg.sha512", + "runtime.win7.System.Console.nuspec", + "ref/dotnet/_._", + "runtimes/win7/lib/dotnet/System.Console.dll" + ] + }, + "runtime.win7.System.IO.Pipes/4.0.0-beta-23413": { + "serviceable": true, + "sha512": "HW+dcHAquKwge1vPkMTHd/zNJngadYMY3zc/4DVDsq/PRWzW01q7v0olmvjwc/ZNLrq5JAdKd3r1Q/lpsuyHJA==", + "files": [ + "runtime.win7.System.IO.Pipes.4.0.0-beta-23413.nupkg", + "runtime.win7.System.IO.Pipes.4.0.0-beta-23413.nupkg.sha512", + "runtime.win7.System.IO.Pipes.nuspec", + "ref/dotnet/_._", + "runtimes/win7/lib/dotnet/System.IO.Pipes.dll" + ] + }, + "System.Collections/4.0.10": { + "sha512": "ux6ilcZZjV/Gp7JEZpe+2V1eTueq6NuoGRM3eZCFuPM25hLVVgCRuea6STW8hvqreIOE59irJk5/ovpA5xQipw==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Collections.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Collections.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/b4f8061406e54dbda8f11b23186be11a.psmdcp", + "ref/dotnet/de/System.Collections.xml", + "ref/dotnet/es/System.Collections.xml", + "ref/dotnet/fr/System.Collections.xml", + "ref/dotnet/it/System.Collections.xml", + "ref/dotnet/ja/System.Collections.xml", + "ref/dotnet/ko/System.Collections.xml", + "ref/dotnet/ru/System.Collections.xml", + "ref/dotnet/System.Collections.dll", + "ref/dotnet/System.Collections.xml", + "ref/dotnet/zh-hans/System.Collections.xml", + "ref/dotnet/zh-hant/System.Collections.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Collections.dll", + "System.Collections.nuspec" + ] + }, + "System.Collections/4.0.11-beta-23413": { + "serviceable": true, + "sha512": "GtOvKwziKL5iVrxdDnIoMpH1vRnnUR9iyYMSeeJB6tgCyXGpKt8zMjBXXnD9UHFALjUioYMX8AoU0mjnzfEkiQ==", + "files": [ + "System.Collections.4.0.11-beta-23413.nupkg", + "System.Collections.4.0.11-beta-23413.nupkg.sha512", + "System.Collections.nuspec", + "lib/DNXCore50/System.Collections.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Collections.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Collections.dll", + "ref/dotnet/System.Collections.xml", + "ref/dotnet/de/System.Collections.xml", + "ref/dotnet/es/System.Collections.xml", + "ref/dotnet/fr/System.Collections.xml", + "ref/dotnet/it/System.Collections.xml", + "ref/dotnet/ja/System.Collections.xml", + "ref/dotnet/ko/System.Collections.xml", + "ref/dotnet/ru/System.Collections.xml", + "ref/dotnet/zh-hans/System.Collections.xml", + "ref/dotnet/zh-hant/System.Collections.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Collections.dll" + ] + }, + "System.Console/4.0.0-beta-23413": { + "serviceable": true, + "sha512": "7Iy21hAO/kaxNXC9En4Pa/O/gSEoGTXN7TlwxM0pXQT9ub+a1eBgmkzDBloXtBf8epk1BowpcMhVWsGfSDv2fw==", + "files": [ + "runtime.json", + "System.Console.4.0.0-beta-23413.nupkg", + "System.Console.4.0.0-beta-23413.nupkg.sha512", + "System.Console.nuspec", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.Console.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Console.dll", + "ref/dotnet/System.Console.xml", + "ref/dotnet/de/System.Console.xml", + "ref/dotnet/es/System.Console.xml", + "ref/dotnet/fr/System.Console.xml", + "ref/dotnet/it/System.Console.xml", + "ref/dotnet/ja/System.Console.xml", + "ref/dotnet/ko/System.Console.xml", + "ref/dotnet/ru/System.Console.xml", + "ref/dotnet/zh-hans/System.Console.xml", + "ref/dotnet/zh-hant/System.Console.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.Console.dll", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._" + ] + }, + "System.Diagnostics.Debug/4.0.10": { + "sha512": "pi2KthuvI2LWV2c2V+fwReDsDiKpNl040h6DcwFOb59SafsPT/V1fCy0z66OKwysurJkBMmp5j5CBe3Um+ub0g==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Diagnostics.Debug.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Diagnostics.Debug.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/bfb05c26051f4a5f9015321db9cb045c.psmdcp", + "ref/dotnet/de/System.Diagnostics.Debug.xml", + "ref/dotnet/es/System.Diagnostics.Debug.xml", + "ref/dotnet/fr/System.Diagnostics.Debug.xml", + "ref/dotnet/it/System.Diagnostics.Debug.xml", + "ref/dotnet/ja/System.Diagnostics.Debug.xml", + "ref/dotnet/ko/System.Diagnostics.Debug.xml", + "ref/dotnet/ru/System.Diagnostics.Debug.xml", + "ref/dotnet/System.Diagnostics.Debug.dll", + "ref/dotnet/System.Diagnostics.Debug.xml", + "ref/dotnet/zh-hans/System.Diagnostics.Debug.xml", + "ref/dotnet/zh-hant/System.Diagnostics.Debug.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Diagnostics.Debug.dll", + "System.Diagnostics.Debug.nuspec" + ] + }, + "System.Diagnostics.Debug/4.0.11-beta-23413": { + "serviceable": true, + "sha512": "YRY+AivzB4LbF/2kULG37n+acGPPSm3jlxO5gGYAIgqaBZqdK20TzfBqWQaeFTq5y4/TGQLHt7ytWianbHEmgw==", + "files": [ + "runtime.json", + "System.Diagnostics.Debug.4.0.11-beta-23413.nupkg", + "System.Diagnostics.Debug.4.0.11-beta-23413.nupkg.sha512", + "System.Diagnostics.Debug.nuspec", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Diagnostics.Debug.dll", + "ref/dotnet/System.Diagnostics.Debug.xml", + "ref/dotnet/de/System.Diagnostics.Debug.xml", + "ref/dotnet/es/System.Diagnostics.Debug.xml", + "ref/dotnet/fr/System.Diagnostics.Debug.xml", + "ref/dotnet/it/System.Diagnostics.Debug.xml", + "ref/dotnet/ja/System.Diagnostics.Debug.xml", + "ref/dotnet/ko/System.Diagnostics.Debug.xml", + "ref/dotnet/ru/System.Diagnostics.Debug.xml", + "ref/dotnet/zh-hans/System.Diagnostics.Debug.xml", + "ref/dotnet/zh-hant/System.Diagnostics.Debug.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._" + ] + }, + "System.Diagnostics.Tools/4.0.1-beta-23413": { + "serviceable": true, + "sha512": "CKIEXJKQJa2AaiyKrVEYBUtrTJbuDhrIOwhUQbkNaQnIw445bt29yLup0lK9oTfx8KAYk2WHXuAwS7DB3enY0Q==", + "files": [ + "System.Diagnostics.Tools.4.0.1-beta-23413.nupkg", + "System.Diagnostics.Tools.4.0.1-beta-23413.nupkg.sha512", + "System.Diagnostics.Tools.nuspec", + "lib/DNXCore50/System.Diagnostics.Tools.dll", + "lib/net45/_._", + "lib/netcore50/System.Diagnostics.Tools.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "ref/dotnet/System.Diagnostics.Tools.dll", + "ref/dotnet/System.Diagnostics.Tools.xml", + "ref/dotnet/de/System.Diagnostics.Tools.xml", + "ref/dotnet/es/System.Diagnostics.Tools.xml", + "ref/dotnet/fr/System.Diagnostics.Tools.xml", + "ref/dotnet/it/System.Diagnostics.Tools.xml", + "ref/dotnet/ja/System.Diagnostics.Tools.xml", + "ref/dotnet/ko/System.Diagnostics.Tools.xml", + "ref/dotnet/ru/System.Diagnostics.Tools.xml", + "ref/dotnet/zh-hans/System.Diagnostics.Tools.xml", + "ref/dotnet/zh-hant/System.Diagnostics.Tools.xml", + "ref/net45/_._", + "ref/netcore50/System.Diagnostics.Tools.dll", + "ref/netcore50/System.Diagnostics.Tools.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "runtimes/win8-aot/lib/netcore50/System.Diagnostics.Tools.dll" + ] + }, + "System.Globalization/4.0.0": { + "sha512": "IBJyTo1y7ZtzzoJUA60T1XPvNTyw/wfFmjFoBFtlYfkekIOtD/AzDDIg0YdUa7eNtFEfliED2R7HdppTdU4t5A==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "License.rtf", + "package/services/metadata/core-properties/7256e6303e10459782eff20c3ec90af5.psmdcp", + "ref/dotnet/de/System.Globalization.xml", + "ref/dotnet/es/System.Globalization.xml", + "ref/dotnet/fr/System.Globalization.xml", + "ref/dotnet/it/System.Globalization.xml", + "ref/dotnet/ja/System.Globalization.xml", + "ref/dotnet/ko/System.Globalization.xml", + "ref/dotnet/ru/System.Globalization.xml", + "ref/dotnet/System.Globalization.dll", + "ref/dotnet/System.Globalization.xml", + "ref/dotnet/zh-hans/System.Globalization.xml", + "ref/dotnet/zh-hant/System.Globalization.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/de/System.Globalization.xml", + "ref/netcore50/es/System.Globalization.xml", + "ref/netcore50/fr/System.Globalization.xml", + "ref/netcore50/it/System.Globalization.xml", + "ref/netcore50/ja/System.Globalization.xml", + "ref/netcore50/ko/System.Globalization.xml", + "ref/netcore50/ru/System.Globalization.xml", + "ref/netcore50/System.Globalization.dll", + "ref/netcore50/System.Globalization.xml", + "ref/netcore50/zh-hans/System.Globalization.xml", + "ref/netcore50/zh-hant/System.Globalization.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "System.Globalization.nuspec" + ] + }, + "System.Globalization/4.0.11-beta-23413": { + "serviceable": true, + "sha512": "v8rq7GqeYr08M5aFl+nj3Bv/aCc5nq314rkKbmhRxZGYNYhTpT6ODCWtU3DT9u2DXOZbmovv8ApZUKt215OrJw==", + "files": [ + "System.Globalization.4.0.11-beta-23413.nupkg", + "System.Globalization.4.0.11-beta-23413.nupkg.sha512", + "System.Globalization.nuspec", + "lib/DNXCore50/System.Globalization.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Globalization.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Globalization.dll", + "ref/dotnet/System.Globalization.xml", + "ref/dotnet/de/System.Globalization.xml", + "ref/dotnet/es/System.Globalization.xml", + "ref/dotnet/fr/System.Globalization.xml", + "ref/dotnet/it/System.Globalization.xml", + "ref/dotnet/ja/System.Globalization.xml", + "ref/dotnet/ko/System.Globalization.xml", + "ref/dotnet/ru/System.Globalization.xml", + "ref/dotnet/zh-hans/System.Globalization.xml", + "ref/dotnet/zh-hant/System.Globalization.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Globalization.dll" + ] + }, + "System.IO/4.0.10": { + "sha512": "kghf1CeYT+W2lw8a50/GxFz5HR9t6RkL4BvjxtTp1NxtEFWywnMA9W8FH/KYXiDNThcw9u/GOViDON4iJFGXIQ==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.IO.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.IO.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/db72fd58a86b4d13a6d2858ebec46705.psmdcp", + "ref/dotnet/de/System.IO.xml", + "ref/dotnet/es/System.IO.xml", + "ref/dotnet/fr/System.IO.xml", + "ref/dotnet/it/System.IO.xml", + "ref/dotnet/ja/System.IO.xml", + "ref/dotnet/ko/System.IO.xml", + "ref/dotnet/ru/System.IO.xml", + "ref/dotnet/System.IO.dll", + "ref/dotnet/System.IO.xml", + "ref/dotnet/zh-hans/System.IO.xml", + "ref/dotnet/zh-hant/System.IO.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.IO.dll", + "System.IO.nuspec" + ] + }, + "System.IO/4.0.11-beta-23413": { + "serviceable": true, + "sha512": "T3GUTbLpvFadw6B0x4FCsAcZVb6xjP5rKSbJJ1AEWp/JB6nDCIzhB8Ai3rhfF2gq6gdkLNC6KzVe9jtkikTa8Q==", + "files": [ + "System.IO.4.0.11-beta-23413.nupkg", + "System.IO.4.0.11-beta-23413.nupkg.sha512", + "System.IO.nuspec", + "lib/DNXCore50/System.IO.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.IO.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.IO.dll", + "ref/dotnet/System.IO.xml", + "ref/dotnet/de/System.IO.xml", + "ref/dotnet/es/System.IO.xml", + "ref/dotnet/fr/System.IO.xml", + "ref/dotnet/it/System.IO.xml", + "ref/dotnet/ja/System.IO.xml", + "ref/dotnet/ko/System.IO.xml", + "ref/dotnet/ru/System.IO.xml", + "ref/dotnet/zh-hans/System.IO.xml", + "ref/dotnet/zh-hant/System.IO.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.IO.dll" + ] + }, + "System.IO.FileSystem/4.0.0": { + "sha512": "eo05SPWfG+54UA0wxgRIYOuOslq+2QrJLXZaJDDsfLXG15OLguaItW39NYZTqUb4DeGOkU4R0wpOLOW4ynMUDQ==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.IO.FileSystem.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.IO.FileSystem.dll", + "lib/netcore50/System.IO.FileSystem.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/0405bad2bcdd403884f42a0a79534bc1.psmdcp", + "ref/dotnet/de/System.IO.FileSystem.xml", + "ref/dotnet/es/System.IO.FileSystem.xml", + "ref/dotnet/fr/System.IO.FileSystem.xml", + "ref/dotnet/it/System.IO.FileSystem.xml", + "ref/dotnet/ja/System.IO.FileSystem.xml", + "ref/dotnet/ko/System.IO.FileSystem.xml", + "ref/dotnet/ru/System.IO.FileSystem.xml", + "ref/dotnet/System.IO.FileSystem.dll", + "ref/dotnet/System.IO.FileSystem.xml", + "ref/dotnet/zh-hans/System.IO.FileSystem.xml", + "ref/dotnet/zh-hant/System.IO.FileSystem.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.IO.FileSystem.dll", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "System.IO.FileSystem.nuspec" + ] + }, + "System.IO.FileSystem/4.0.1-beta-23413": { + "serviceable": true, + "sha512": "502/K3UnVB5icsENDa1JN3zbyu5tN28Mi8utXLD2C7+ZW9sX/YrGP0fnTkSk1H7A3rS1Ve8hUcOvV6SBoY9D1A==", + "files": [ + "runtime.json", + "System.IO.FileSystem.4.0.1-beta-23413.nupkg", + "System.IO.FileSystem.4.0.1-beta-23413.nupkg.sha512", + "System.IO.FileSystem.nuspec", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.IO.FileSystem.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.IO.FileSystem.dll", + "ref/dotnet/System.IO.FileSystem.xml", + "ref/dotnet/de/System.IO.FileSystem.xml", + "ref/dotnet/es/System.IO.FileSystem.xml", + "ref/dotnet/fr/System.IO.FileSystem.xml", + "ref/dotnet/it/System.IO.FileSystem.xml", + "ref/dotnet/ja/System.IO.FileSystem.xml", + "ref/dotnet/ko/System.IO.FileSystem.xml", + "ref/dotnet/ru/System.IO.FileSystem.xml", + "ref/dotnet/zh-hans/System.IO.FileSystem.xml", + "ref/dotnet/zh-hant/System.IO.FileSystem.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.IO.FileSystem.dll", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._" + ] + }, + "System.IO.FileSystem.Primitives/4.0.0": { + "sha512": "7pJUvYi/Yq3A5nagqCCiOw3+aJp3xXc/Cjr8dnJDnER3/6kX3LEencfqmXUcPl9+7OvRNyPMNhqsLAcMK6K/KA==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/dotnet/System.IO.FileSystem.Primitives.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.IO.FileSystem.Primitives.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/2cf3542156f0426483f92b9e37d8d381.psmdcp", + "ref/dotnet/de/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/es/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/fr/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/it/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/ja/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/ko/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/ru/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/System.IO.FileSystem.Primitives.dll", + "ref/dotnet/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/zh-hans/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/zh-hant/System.IO.FileSystem.Primitives.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.IO.FileSystem.Primitives.dll", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "System.IO.FileSystem.Primitives.nuspec" + ] + }, + "System.IO.FileSystem.Primitives/4.0.1-beta-23413": { + "serviceable": true, + "sha512": "9XBViavkgTVlhaufDVvhzSHdQsCP5pqcjHhpF9Anb5VcilJWGDRbpxeknoU+OLr4tMUERSxlBa3LT4sRbcpXTw==", + "files": [ + "System.IO.FileSystem.Primitives.4.0.1-beta-23413.nupkg", + "System.IO.FileSystem.Primitives.4.0.1-beta-23413.nupkg.sha512", + "System.IO.FileSystem.Primitives.nuspec", + "lib/dotnet/System.IO.FileSystem.Primitives.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/System.IO.FileSystem.Primitives.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.IO.FileSystem.Primitives.dll", + "ref/dotnet/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/de/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/es/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/fr/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/it/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/ja/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/ko/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/ru/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/zh-hans/System.IO.FileSystem.Primitives.xml", + "ref/dotnet/zh-hant/System.IO.FileSystem.Primitives.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/System.IO.FileSystem.Primitives.dll", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._" + ] + }, + "System.IO.Pipes/4.0.0-beta-23413": { + "serviceable": true, + "sha512": "GNRjCBuaTi5WJYJ6PwIyXY8mYene2vHgltska3OzfSizxlGTjS3MKfG29yQPZ7njdWtt1s7xEQpsSJoppmZZRg==", + "files": [ + "runtime.json", + "System.IO.Pipes.4.0.0-beta-23413.nupkg", + "System.IO.Pipes.4.0.0-beta-23413.nupkg.sha512", + "System.IO.Pipes.nuspec", + "lib/net46/System.IO.Pipes.dll", + "ref/dotnet/System.IO.Pipes.dll", + "ref/dotnet/System.IO.Pipes.xml", + "ref/dotnet/de/System.IO.Pipes.xml", + "ref/dotnet/es/System.IO.Pipes.xml", + "ref/dotnet/fr/System.IO.Pipes.xml", + "ref/dotnet/it/System.IO.Pipes.xml", + "ref/dotnet/ja/System.IO.Pipes.xml", + "ref/dotnet/ko/System.IO.Pipes.xml", + "ref/dotnet/ru/System.IO.Pipes.xml", + "ref/dotnet/zh-hans/System.IO.Pipes.xml", + "ref/dotnet/zh-hant/System.IO.Pipes.xml", + "ref/net46/System.IO.Pipes.dll" + ] + }, + "System.Linq/4.0.0": { + "sha512": "r6Hlc+ytE6m/9UBr+nNRRdoJEWjoeQiT3L3lXYFDHoXk3VYsRBCDNXrawcexw7KPLaH0zamQLiAb6avhZ50cGg==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/dotnet/System.Linq.dll", + "lib/net45/_._", + "lib/netcore50/System.Linq.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "package/services/metadata/core-properties/6fcde56ce4094f6a8fff4b28267da532.psmdcp", + "ref/dotnet/de/System.Linq.xml", + "ref/dotnet/es/System.Linq.xml", + "ref/dotnet/fr/System.Linq.xml", + "ref/dotnet/it/System.Linq.xml", + "ref/dotnet/ja/System.Linq.xml", + "ref/dotnet/ko/System.Linq.xml", + "ref/dotnet/ru/System.Linq.xml", + "ref/dotnet/System.Linq.dll", + "ref/dotnet/System.Linq.xml", + "ref/dotnet/zh-hans/System.Linq.xml", + "ref/dotnet/zh-hant/System.Linq.xml", + "ref/net45/_._", + "ref/netcore50/System.Linq.dll", + "ref/netcore50/System.Linq.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "System.Linq.nuspec" + ] + }, + "System.Linq/4.0.1-beta-23413": { + "serviceable": true, + "sha512": "vxYuhKvMuaeG7ffEBmhvFiVYECpaaMx3yRakd+6RGECw21c7xkM2sk5gNbhnOCbbav5mIZTE1qgrHCGQjb6+oQ==", + "files": [ + "System.Linq.4.0.1-beta-23413.nupkg", + "System.Linq.4.0.1-beta-23413.nupkg.sha512", + "System.Linq.nuspec", + "lib/dotnet/System.Linq.dll", + "lib/net45/_._", + "lib/netcore50/System.Linq.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "ref/dotnet/System.Linq.dll", + "ref/dotnet/System.Linq.xml", + "ref/dotnet/de/System.Linq.xml", + "ref/dotnet/es/System.Linq.xml", + "ref/dotnet/fr/System.Linq.xml", + "ref/dotnet/it/System.Linq.xml", + "ref/dotnet/ja/System.Linq.xml", + "ref/dotnet/ko/System.Linq.xml", + "ref/dotnet/ru/System.Linq.xml", + "ref/dotnet/zh-hans/System.Linq.xml", + "ref/dotnet/zh-hant/System.Linq.xml", + "ref/net45/_._", + "ref/netcore50/System.Linq.dll", + "ref/netcore50/System.Linq.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._" + ] + }, + "System.Private.Uri/4.0.0": { + "sha512": "CtuxaCKcRIvPcsqquVl3mPp79EDZPMr2UogfiFCxCs+t2z1VjbpQsKNs1GHZ8VQetqbk1mr0V1yAfMe6y8CHDA==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Private.Uri.dll", + "lib/netcore50/System.Private.Uri.dll", + "package/services/metadata/core-properties/86377e21a22d44bbba860094428d894c.psmdcp", + "ref/dnxcore50/_._", + "ref/netcore50/_._", + "runtimes/win8-aot/lib/netcore50/System.Private.Uri.dll", + "System.Private.Uri.nuspec" + ] + }, + "System.Private.Uri/4.0.1-beta-23413": { + "sha512": "jwvNUmwJYlU7gf8tjYI6jlzhp2gNwZ/HVEJoJgRDiUlDSDVWi0yepXpfC0Kfpph2QbIeLIW2E4QRxPhwlc3Eww==", + "files": [ + "runtime.json", + "System.Private.Uri.4.0.1-beta-23413.nupkg", + "System.Private.Uri.4.0.1-beta-23413.nupkg.sha512", + "System.Private.Uri.nuspec", + "ref/dnxcore50/_._", + "ref/netcore50/_._" + ] + }, + "System.Reflection/4.0.0": { + "sha512": "g96Rn8XuG7y4VfxPj/jnXroRJdQ8L3iN3k3zqsuzk4k3Nq4KMXARYiIO4BLW4GwX06uQpuYwRMcAC/aF117knQ==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net45/_._", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "License.rtf", + "package/services/metadata/core-properties/1e935117d401458384a90c2c69f60bd2.psmdcp", + "ref/dotnet/de/System.Reflection.xml", + "ref/dotnet/es/System.Reflection.xml", + "ref/dotnet/fr/System.Reflection.xml", + "ref/dotnet/it/System.Reflection.xml", + "ref/dotnet/ja/System.Reflection.xml", + "ref/dotnet/ko/System.Reflection.xml", + "ref/dotnet/ru/System.Reflection.xml", + "ref/dotnet/System.Reflection.dll", + "ref/dotnet/System.Reflection.xml", + "ref/dotnet/zh-hans/System.Reflection.xml", + "ref/dotnet/zh-hant/System.Reflection.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net45/_._", + "ref/netcore50/de/System.Reflection.xml", + "ref/netcore50/es/System.Reflection.xml", + "ref/netcore50/fr/System.Reflection.xml", + "ref/netcore50/it/System.Reflection.xml", + "ref/netcore50/ja/System.Reflection.xml", + "ref/netcore50/ko/System.Reflection.xml", + "ref/netcore50/ru/System.Reflection.xml", + "ref/netcore50/System.Reflection.dll", + "ref/netcore50/System.Reflection.xml", + "ref/netcore50/zh-hans/System.Reflection.xml", + "ref/netcore50/zh-hant/System.Reflection.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "System.Reflection.nuspec" + ] + }, + "System.Reflection/4.1.0-beta-23413": { + "serviceable": true, + "sha512": "/A2UNQB8CM+WWiGVICXgEsGZ1mGySCc0BBTKnpvsWsucpbvXmvztMJi1a3Mqedy8mYfvMmBPQiP0nyN+Z/gHLg==", + "files": [ + "System.Reflection.4.1.0-beta-23413.nupkg", + "System.Reflection.4.1.0-beta-23413.nupkg.sha512", + "System.Reflection.nuspec", + "lib/DNXCore50/System.Reflection.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Reflection.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Reflection.dll", + "ref/dotnet/System.Reflection.xml", + "ref/dotnet/de/System.Reflection.xml", + "ref/dotnet/es/System.Reflection.xml", + "ref/dotnet/fr/System.Reflection.xml", + "ref/dotnet/it/System.Reflection.xml", + "ref/dotnet/ja/System.Reflection.xml", + "ref/dotnet/ko/System.Reflection.xml", + "ref/dotnet/ru/System.Reflection.xml", + "ref/dotnet/zh-hans/System.Reflection.xml", + "ref/dotnet/zh-hant/System.Reflection.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Reflection.dll" + ] + }, + "System.Reflection.Primitives/4.0.0": { + "sha512": "n9S0XpKv2ruc17FSnaiX6nV47VfHTZ1wLjKZlAirUZCvDQCH71mVp+Ohabn0xXLh5pK2PKp45HCxkqu5Fxn/lA==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Reflection.Primitives.dll", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Primitives.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "package/services/metadata/core-properties/7070509f3bfd418d859635361251dab0.psmdcp", + "ref/dotnet/de/System.Reflection.Primitives.xml", + "ref/dotnet/es/System.Reflection.Primitives.xml", + "ref/dotnet/fr/System.Reflection.Primitives.xml", + "ref/dotnet/it/System.Reflection.Primitives.xml", + "ref/dotnet/ja/System.Reflection.Primitives.xml", + "ref/dotnet/ko/System.Reflection.Primitives.xml", + "ref/dotnet/ru/System.Reflection.Primitives.xml", + "ref/dotnet/System.Reflection.Primitives.dll", + "ref/dotnet/System.Reflection.Primitives.xml", + "ref/dotnet/zh-hans/System.Reflection.Primitives.xml", + "ref/dotnet/zh-hant/System.Reflection.Primitives.xml", + "ref/net45/_._", + "ref/netcore50/System.Reflection.Primitives.dll", + "ref/netcore50/System.Reflection.Primitives.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "runtimes/win8-aot/lib/netcore50/System.Reflection.Primitives.dll", + "System.Reflection.Primitives.nuspec" + ] + }, + "System.Reflection.Primitives/4.0.1-beta-23413": { + "serviceable": true, + "sha512": "vxOTfLeg6ViYt+4Bxnoz/ZGpDlicmq8zgTZMHhnZgXeq3UKdNJBWGhC1G/iekzFJdvU9Fdep6rNZOlLcATJUjA==", + "files": [ + "System.Reflection.Primitives.4.0.1-beta-23413.nupkg", + "System.Reflection.Primitives.4.0.1-beta-23413.nupkg.sha512", + "System.Reflection.Primitives.nuspec", + "lib/DNXCore50/System.Reflection.Primitives.dll", + "lib/net45/_._", + "lib/netcore50/System.Reflection.Primitives.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "ref/dotnet/System.Reflection.Primitives.dll", + "ref/dotnet/System.Reflection.Primitives.xml", + "ref/dotnet/de/System.Reflection.Primitives.xml", + "ref/dotnet/es/System.Reflection.Primitives.xml", + "ref/dotnet/fr/System.Reflection.Primitives.xml", + "ref/dotnet/it/System.Reflection.Primitives.xml", + "ref/dotnet/ja/System.Reflection.Primitives.xml", + "ref/dotnet/ko/System.Reflection.Primitives.xml", + "ref/dotnet/ru/System.Reflection.Primitives.xml", + "ref/dotnet/zh-hans/System.Reflection.Primitives.xml", + "ref/dotnet/zh-hant/System.Reflection.Primitives.xml", + "ref/net45/_._", + "ref/netcore50/System.Reflection.Primitives.dll", + "ref/netcore50/System.Reflection.Primitives.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "runtimes/win8-aot/lib/netcore50/System.Reflection.Primitives.dll" + ] + }, + "System.Resources.ResourceManager/4.0.0": { + "sha512": "qmqeZ4BJgjfU+G2JbrZt4Dk1LsMxO4t+f/9HarNY6w8pBgweO6jT+cknUH7c3qIrGvyUqraBhU45Eo6UtA0fAw==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Resources.ResourceManager.dll", + "lib/net45/_._", + "lib/netcore50/System.Resources.ResourceManager.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "package/services/metadata/core-properties/657a73ee3f09479c9fedb9538ade8eac.psmdcp", + "ref/dotnet/de/System.Resources.ResourceManager.xml", + "ref/dotnet/es/System.Resources.ResourceManager.xml", + "ref/dotnet/fr/System.Resources.ResourceManager.xml", + "ref/dotnet/it/System.Resources.ResourceManager.xml", + "ref/dotnet/ja/System.Resources.ResourceManager.xml", + "ref/dotnet/ko/System.Resources.ResourceManager.xml", + "ref/dotnet/ru/System.Resources.ResourceManager.xml", + "ref/dotnet/System.Resources.ResourceManager.dll", + "ref/dotnet/System.Resources.ResourceManager.xml", + "ref/dotnet/zh-hans/System.Resources.ResourceManager.xml", + "ref/dotnet/zh-hant/System.Resources.ResourceManager.xml", + "ref/net45/_._", + "ref/netcore50/System.Resources.ResourceManager.dll", + "ref/netcore50/System.Resources.ResourceManager.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "runtimes/win8-aot/lib/netcore50/System.Resources.ResourceManager.dll", + "System.Resources.ResourceManager.nuspec" + ] + }, + "System.Resources.ResourceManager/4.0.1-beta-23413": { + "serviceable": true, + "sha512": "Z4ArP0eFmgd1GbGKJtviivD+AyhAKpZtaewgzk0Knf9IJ+VyEOFvye3/M3grDwCQFjr4L568kifeO4ErUPNq1Q==", + "files": [ + "System.Resources.ResourceManager.4.0.1-beta-23413.nupkg", + "System.Resources.ResourceManager.4.0.1-beta-23413.nupkg.sha512", + "System.Resources.ResourceManager.nuspec", + "lib/DNXCore50/System.Resources.ResourceManager.dll", + "lib/net45/_._", + "lib/netcore50/System.Resources.ResourceManager.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "ref/dotnet/System.Resources.ResourceManager.dll", + "ref/dotnet/System.Resources.ResourceManager.xml", + "ref/dotnet/de/System.Resources.ResourceManager.xml", + "ref/dotnet/es/System.Resources.ResourceManager.xml", + "ref/dotnet/fr/System.Resources.ResourceManager.xml", + "ref/dotnet/it/System.Resources.ResourceManager.xml", + "ref/dotnet/ja/System.Resources.ResourceManager.xml", + "ref/dotnet/ko/System.Resources.ResourceManager.xml", + "ref/dotnet/ru/System.Resources.ResourceManager.xml", + "ref/dotnet/zh-hans/System.Resources.ResourceManager.xml", + "ref/dotnet/zh-hant/System.Resources.ResourceManager.xml", + "ref/net45/_._", + "ref/netcore50/System.Resources.ResourceManager.dll", + "ref/netcore50/System.Resources.ResourceManager.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "runtimes/win8-aot/lib/netcore50/System.Resources.ResourceManager.dll" + ] + }, + "System.Runtime/4.0.20": { + "sha512": "X7N/9Bz7jVPorqdVFO86ns1sX6MlQM+WTxELtx+Z4VG45x9+LKmWH0GRqjgKprUnVuwmfB9EJ9DQng14Z7/zwg==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Runtime.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Runtime.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/d1ded52f75da4446b1c962f9292aa3ef.psmdcp", + "ref/dotnet/de/System.Runtime.xml", + "ref/dotnet/es/System.Runtime.xml", + "ref/dotnet/fr/System.Runtime.xml", + "ref/dotnet/it/System.Runtime.xml", + "ref/dotnet/ja/System.Runtime.xml", + "ref/dotnet/ko/System.Runtime.xml", + "ref/dotnet/ru/System.Runtime.xml", + "ref/dotnet/System.Runtime.dll", + "ref/dotnet/System.Runtime.xml", + "ref/dotnet/zh-hans/System.Runtime.xml", + "ref/dotnet/zh-hant/System.Runtime.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Runtime.dll", + "System.Runtime.nuspec" + ] + }, + "System.Runtime/4.0.21-beta-23413": { + "serviceable": true, + "sha512": "Q/97nVZaPDNf0Rh9vWohXVxPB11sQ6E5CV7ZXEAjckSJ8pIfAu+RCYliHegNMXhQnfpO+X63861GVjNicH5fYg==", + "files": [ + "System.Runtime.4.0.21-beta-23413.nupkg", + "System.Runtime.4.0.21-beta-23413.nupkg.sha512", + "System.Runtime.nuspec", + "lib/DNXCore50/System.Runtime.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Runtime.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Runtime.dll", + "ref/dotnet/System.Runtime.xml", + "ref/dotnet/de/System.Runtime.xml", + "ref/dotnet/es/System.Runtime.xml", + "ref/dotnet/fr/System.Runtime.xml", + "ref/dotnet/it/System.Runtime.xml", + "ref/dotnet/ja/System.Runtime.xml", + "ref/dotnet/ko/System.Runtime.xml", + "ref/dotnet/ru/System.Runtime.xml", + "ref/dotnet/zh-hans/System.Runtime.xml", + "ref/dotnet/zh-hant/System.Runtime.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Runtime.dll" + ] + }, + "System.Runtime.Extensions/4.0.10": { + "sha512": "5dsEwf3Iml7d5OZeT20iyOjT+r+okWpN7xI2v+R4cgd3WSj4DeRPTvPFjDpacbVW4skCAZ8B9hxXJYgkCFKJ1A==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Runtime.Extensions.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Runtime.Extensions.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/c7fee76a13d04c7ea49fb1a24c184f37.psmdcp", + "ref/dotnet/de/System.Runtime.Extensions.xml", + "ref/dotnet/es/System.Runtime.Extensions.xml", + "ref/dotnet/fr/System.Runtime.Extensions.xml", + "ref/dotnet/it/System.Runtime.Extensions.xml", + "ref/dotnet/ja/System.Runtime.Extensions.xml", + "ref/dotnet/ko/System.Runtime.Extensions.xml", + "ref/dotnet/ru/System.Runtime.Extensions.xml", + "ref/dotnet/System.Runtime.Extensions.dll", + "ref/dotnet/System.Runtime.Extensions.xml", + "ref/dotnet/zh-hans/System.Runtime.Extensions.xml", + "ref/dotnet/zh-hant/System.Runtime.Extensions.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Runtime.Extensions.dll", + "System.Runtime.Extensions.nuspec" + ] + }, + "System.Runtime.Extensions/4.0.11-beta-23413": { + "serviceable": true, + "sha512": "V7ds0WOQi7YqpGJ1lVFjY5aU6wWcjmrlyizAU4eAsuvj0/gC4cipYsbRHDdQ6EQjIC3G1CCyhJwjyEP/O6hE/w==", + "files": [ + "runtime.json", + "System.Runtime.Extensions.4.0.11-beta-23413.nupkg", + "System.Runtime.Extensions.4.0.11-beta-23413.nupkg.sha512", + "System.Runtime.Extensions.nuspec", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Runtime.Extensions.dll", + "ref/dotnet/System.Runtime.Extensions.xml", + "ref/dotnet/de/System.Runtime.Extensions.xml", + "ref/dotnet/es/System.Runtime.Extensions.xml", + "ref/dotnet/fr/System.Runtime.Extensions.xml", + "ref/dotnet/it/System.Runtime.Extensions.xml", + "ref/dotnet/ja/System.Runtime.Extensions.xml", + "ref/dotnet/ko/System.Runtime.Extensions.xml", + "ref/dotnet/ru/System.Runtime.Extensions.xml", + "ref/dotnet/zh-hans/System.Runtime.Extensions.xml", + "ref/dotnet/zh-hant/System.Runtime.Extensions.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._" + ] + }, + "System.Runtime.Handles/4.0.0": { + "sha512": "638VhpRq63tVcQ6HDb3um3R/J2BtR1Sa96toHo6PcJGPXEPEsleCuqhBgX2gFCz0y0qkutANwW6VPPY5wQu1XQ==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Runtime.Handles.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Runtime.Handles.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/da57aa32ff2441d1acfe85bee4f101ab.psmdcp", + "ref/dotnet/de/System.Runtime.Handles.xml", + "ref/dotnet/es/System.Runtime.Handles.xml", + "ref/dotnet/fr/System.Runtime.Handles.xml", + "ref/dotnet/it/System.Runtime.Handles.xml", + "ref/dotnet/ja/System.Runtime.Handles.xml", + "ref/dotnet/ko/System.Runtime.Handles.xml", + "ref/dotnet/ru/System.Runtime.Handles.xml", + "ref/dotnet/System.Runtime.Handles.dll", + "ref/dotnet/System.Runtime.Handles.xml", + "ref/dotnet/zh-hans/System.Runtime.Handles.xml", + "ref/dotnet/zh-hant/System.Runtime.Handles.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Runtime.Handles.dll", + "System.Runtime.Handles.nuspec" + ] + }, + "System.Runtime.Handles/4.0.1-beta-23413": { + "serviceable": true, + "sha512": "ydpiRUuS3zMz4xWPIrJY0gb0D9RIXih55tSiNvoisqwGylgQpsU0vGYb1bq8PWRp1qA1iEnbZUGrj7gv8OKU/Q==", + "files": [ + "System.Runtime.Handles.4.0.1-beta-23413.nupkg", + "System.Runtime.Handles.4.0.1-beta-23413.nupkg.sha512", + "System.Runtime.Handles.nuspec", + "lib/DNXCore50/System.Runtime.Handles.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Runtime.Handles.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Runtime.Handles.dll", + "ref/dotnet/System.Runtime.Handles.xml", + "ref/dotnet/de/System.Runtime.Handles.xml", + "ref/dotnet/es/System.Runtime.Handles.xml", + "ref/dotnet/fr/System.Runtime.Handles.xml", + "ref/dotnet/it/System.Runtime.Handles.xml", + "ref/dotnet/ja/System.Runtime.Handles.xml", + "ref/dotnet/ko/System.Runtime.Handles.xml", + "ref/dotnet/ru/System.Runtime.Handles.xml", + "ref/dotnet/zh-hans/System.Runtime.Handles.xml", + "ref/dotnet/zh-hant/System.Runtime.Handles.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Runtime.Handles.dll" + ] + }, + "System.Runtime.InteropServices/4.0.20": { + "sha512": "ZgDyBYfEnjWoz/viS6VOswA6XOkDSH2DzgbpczbW50RywhnCgTl+w3JEvtAiOGyIh8cyx1NJq80jsNBSUr8Pig==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Runtime.InteropServices.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Runtime.InteropServices.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/78e7f61876374acba2a95834f272d262.psmdcp", + "ref/dotnet/de/System.Runtime.InteropServices.xml", + "ref/dotnet/es/System.Runtime.InteropServices.xml", + "ref/dotnet/fr/System.Runtime.InteropServices.xml", + "ref/dotnet/it/System.Runtime.InteropServices.xml", + "ref/dotnet/ja/System.Runtime.InteropServices.xml", + "ref/dotnet/ko/System.Runtime.InteropServices.xml", + "ref/dotnet/ru/System.Runtime.InteropServices.xml", + "ref/dotnet/System.Runtime.InteropServices.dll", + "ref/dotnet/System.Runtime.InteropServices.xml", + "ref/dotnet/zh-hans/System.Runtime.InteropServices.xml", + "ref/dotnet/zh-hant/System.Runtime.InteropServices.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Runtime.InteropServices.dll", + "System.Runtime.InteropServices.nuspec" + ] + }, + "System.Runtime.InteropServices/4.0.21-beta-23413": { + "serviceable": true, + "sha512": "kIi3vuWueO7MdIiuHhkTPUr0bwrAhW7Pnrw35GhATQcm0g25PQoNGaZ4rOzjaIZtO/FOf/9gf7emg2BoAO3Ihw==", + "files": [ + "System.Runtime.InteropServices.4.0.21-beta-23413.nupkg", + "System.Runtime.InteropServices.4.0.21-beta-23413.nupkg.sha512", + "System.Runtime.InteropServices.nuspec", + "lib/DNXCore50/System.Runtime.InteropServices.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Runtime.InteropServices.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Runtime.InteropServices.dll", + "ref/dotnet/System.Runtime.InteropServices.xml", + "ref/dotnet/de/System.Runtime.InteropServices.xml", + "ref/dotnet/es/System.Runtime.InteropServices.xml", + "ref/dotnet/fr/System.Runtime.InteropServices.xml", + "ref/dotnet/it/System.Runtime.InteropServices.xml", + "ref/dotnet/ja/System.Runtime.InteropServices.xml", + "ref/dotnet/ko/System.Runtime.InteropServices.xml", + "ref/dotnet/ru/System.Runtime.InteropServices.xml", + "ref/dotnet/zh-hans/System.Runtime.InteropServices.xml", + "ref/dotnet/zh-hant/System.Runtime.InteropServices.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Runtime.InteropServices.dll" + ] + }, + "System.Security.Principal/4.0.0": { + "sha512": "FOhq3jUOONi6fp5j3nPYJMrKtSJlqAURpjiO3FaDIV4DJNEYymWW5uh1pfxySEB8dtAW+I66IypzNge/w9OzZQ==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/dotnet/System.Security.Principal.dll", + "lib/net45/_._", + "lib/netcore50/System.Security.Principal.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "package/services/metadata/core-properties/5d44fbabc99d4204b6a2f76329d0a184.psmdcp", + "ref/dotnet/de/System.Security.Principal.xml", + "ref/dotnet/es/System.Security.Principal.xml", + "ref/dotnet/fr/System.Security.Principal.xml", + "ref/dotnet/it/System.Security.Principal.xml", + "ref/dotnet/ja/System.Security.Principal.xml", + "ref/dotnet/ko/System.Security.Principal.xml", + "ref/dotnet/ru/System.Security.Principal.xml", + "ref/dotnet/System.Security.Principal.dll", + "ref/dotnet/System.Security.Principal.xml", + "ref/dotnet/zh-hans/System.Security.Principal.xml", + "ref/dotnet/zh-hant/System.Security.Principal.xml", + "ref/net45/_._", + "ref/netcore50/System.Security.Principal.dll", + "ref/netcore50/System.Security.Principal.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._", + "System.Security.Principal.nuspec" + ] + }, + "System.Security.Principal/4.0.1-beta-23413": { + "serviceable": true, + "sha512": "pVHazWbppQiPdZAEpugXZL8qui7nqjqzmrLYxtBlm9ml99+e/gqO2JLwxNu/zaFP5KMTnXRFiX0hSaDStBwDFg==", + "files": [ + "System.Security.Principal.4.0.1-beta-23413.nupkg", + "System.Security.Principal.4.0.1-beta-23413.nupkg.sha512", + "System.Security.Principal.nuspec", + "lib/dotnet/System.Security.Principal.dll", + "lib/net45/_._", + "lib/netcore50/System.Security.Principal.dll", + "lib/win8/_._", + "lib/wp80/_._", + "lib/wpa81/_._", + "ref/dotnet/System.Security.Principal.dll", + "ref/dotnet/System.Security.Principal.xml", + "ref/dotnet/de/System.Security.Principal.xml", + "ref/dotnet/es/System.Security.Principal.xml", + "ref/dotnet/fr/System.Security.Principal.xml", + "ref/dotnet/it/System.Security.Principal.xml", + "ref/dotnet/ja/System.Security.Principal.xml", + "ref/dotnet/ko/System.Security.Principal.xml", + "ref/dotnet/ru/System.Security.Principal.xml", + "ref/dotnet/zh-hans/System.Security.Principal.xml", + "ref/dotnet/zh-hant/System.Security.Principal.xml", + "ref/net45/_._", + "ref/netcore50/System.Security.Principal.dll", + "ref/netcore50/System.Security.Principal.xml", + "ref/win8/_._", + "ref/wp80/_._", + "ref/wpa81/_._" + ] + }, + "System.Text.Encoding/4.0.10": { + "sha512": "fNlSFgy4OuDlJrP9SFFxMlaLazq6ipv15sU5TiEgg9UCVnA/OgoVUfymFp4AOk1jOkW5SVxWbeeIUptcM+m/Vw==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Text.Encoding.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Text.Encoding.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/829e172aadac4937a5a6a4b386855282.psmdcp", + "ref/dotnet/de/System.Text.Encoding.xml", + "ref/dotnet/es/System.Text.Encoding.xml", + "ref/dotnet/fr/System.Text.Encoding.xml", + "ref/dotnet/it/System.Text.Encoding.xml", + "ref/dotnet/ja/System.Text.Encoding.xml", + "ref/dotnet/ko/System.Text.Encoding.xml", + "ref/dotnet/ru/System.Text.Encoding.xml", + "ref/dotnet/System.Text.Encoding.dll", + "ref/dotnet/System.Text.Encoding.xml", + "ref/dotnet/zh-hans/System.Text.Encoding.xml", + "ref/dotnet/zh-hant/System.Text.Encoding.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Text.Encoding.dll", + "System.Text.Encoding.nuspec" + ] + }, + "System.Text.Encoding/4.0.11-beta-23413": { + "serviceable": true, + "sha512": "zgeOS8g3ez1KcvrQkVlrCXjGJLX7lL0pWq1tSI6YlA+8o63nRoGw2TYEz1/5YpAgnMYLwM+BGoVMq8uujZT1YA==", + "files": [ + "System.Text.Encoding.4.0.11-beta-23413.nupkg", + "System.Text.Encoding.4.0.11-beta-23413.nupkg.sha512", + "System.Text.Encoding.nuspec", + "lib/DNXCore50/System.Text.Encoding.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Text.Encoding.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Text.Encoding.dll", + "ref/dotnet/System.Text.Encoding.xml", + "ref/dotnet/de/System.Text.Encoding.xml", + "ref/dotnet/es/System.Text.Encoding.xml", + "ref/dotnet/fr/System.Text.Encoding.xml", + "ref/dotnet/it/System.Text.Encoding.xml", + "ref/dotnet/ja/System.Text.Encoding.xml", + "ref/dotnet/ko/System.Text.Encoding.xml", + "ref/dotnet/ru/System.Text.Encoding.xml", + "ref/dotnet/zh-hans/System.Text.Encoding.xml", + "ref/dotnet/zh-hant/System.Text.Encoding.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Text.Encoding.dll" + ] + }, + "System.Text.Encoding.Extensions/4.0.10": { + "sha512": "TZvlwXMxKo3bSRIcsWZLCIzIhLbvlz+mGeKYRZv/zUiSoQzGOwkYeBu6hOw2XPQgKqT0F4Rv8zqKdvmp2fWKYg==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Text.Encoding.Extensions.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Text.Encoding.Extensions.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/894d51cf918c4bca91e81a732d958707.psmdcp", + "ref/dotnet/de/System.Text.Encoding.Extensions.xml", + "ref/dotnet/es/System.Text.Encoding.Extensions.xml", + "ref/dotnet/fr/System.Text.Encoding.Extensions.xml", + "ref/dotnet/it/System.Text.Encoding.Extensions.xml", + "ref/dotnet/ja/System.Text.Encoding.Extensions.xml", + "ref/dotnet/ko/System.Text.Encoding.Extensions.xml", + "ref/dotnet/ru/System.Text.Encoding.Extensions.xml", + "ref/dotnet/System.Text.Encoding.Extensions.dll", + "ref/dotnet/System.Text.Encoding.Extensions.xml", + "ref/dotnet/zh-hans/System.Text.Encoding.Extensions.xml", + "ref/dotnet/zh-hant/System.Text.Encoding.Extensions.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Text.Encoding.Extensions.dll", + "System.Text.Encoding.Extensions.nuspec" + ] + }, + "System.Text.Encoding.Extensions/4.0.11-beta-23413": { + "serviceable": true, + "sha512": "mCutxUrPG0Q7O+/XrxPa792vaPgHRMT09vR8E5TrlvraVWtIgsrbkG+3qfIXzCUQevJTYMHR9aTs85XvSpIV4w==", + "files": [ + "System.Text.Encoding.Extensions.4.0.11-beta-23413.nupkg", + "System.Text.Encoding.Extensions.4.0.11-beta-23413.nupkg.sha512", + "System.Text.Encoding.Extensions.nuspec", + "lib/DNXCore50/System.Text.Encoding.Extensions.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Text.Encoding.Extensions.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Text.Encoding.Extensions.dll", + "ref/dotnet/System.Text.Encoding.Extensions.xml", + "ref/dotnet/de/System.Text.Encoding.Extensions.xml", + "ref/dotnet/es/System.Text.Encoding.Extensions.xml", + "ref/dotnet/fr/System.Text.Encoding.Extensions.xml", + "ref/dotnet/it/System.Text.Encoding.Extensions.xml", + "ref/dotnet/ja/System.Text.Encoding.Extensions.xml", + "ref/dotnet/ko/System.Text.Encoding.Extensions.xml", + "ref/dotnet/ru/System.Text.Encoding.Extensions.xml", + "ref/dotnet/zh-hans/System.Text.Encoding.Extensions.xml", + "ref/dotnet/zh-hant/System.Text.Encoding.Extensions.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Text.Encoding.Extensions.dll" + ] + }, + "System.Threading/4.0.10": { + "sha512": "0w6pRxIEE7wuiOJeKabkDgeIKmqf4ER1VNrs6qFwHnooEE78yHwi/bKkg5Jo8/pzGLm0xQJw0nEmPXt1QBAIUA==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Threading.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Threading.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/c17c3791d8fa4efbb8aded2ca8c71fbe.psmdcp", + "ref/dotnet/de/System.Threading.xml", + "ref/dotnet/es/System.Threading.xml", + "ref/dotnet/fr/System.Threading.xml", + "ref/dotnet/it/System.Threading.xml", + "ref/dotnet/ja/System.Threading.xml", + "ref/dotnet/ko/System.Threading.xml", + "ref/dotnet/ru/System.Threading.xml", + "ref/dotnet/System.Threading.dll", + "ref/dotnet/System.Threading.xml", + "ref/dotnet/zh-hans/System.Threading.xml", + "ref/dotnet/zh-hant/System.Threading.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Threading.dll", + "System.Threading.nuspec" + ] + }, + "System.Threading/4.0.11-beta-23413": { + "serviceable": true, + "sha512": "x5K+Mto2rkKWYCPdvZMclkbVqI0Km8eDzSgpyLkf8+BdC7O9z/1g+HrMG5PgKZoVWkFH+a+FpzMRXU7Miv4/Uw==", + "files": [ + "runtime.json", + "System.Threading.4.0.11-beta-23413.nupkg", + "System.Threading.4.0.11-beta-23413.nupkg.sha512", + "System.Threading.nuspec", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Threading.dll", + "ref/dotnet/System.Threading.xml", + "ref/dotnet/de/System.Threading.xml", + "ref/dotnet/es/System.Threading.xml", + "ref/dotnet/fr/System.Threading.xml", + "ref/dotnet/it/System.Threading.xml", + "ref/dotnet/ja/System.Threading.xml", + "ref/dotnet/ko/System.Threading.xml", + "ref/dotnet/ru/System.Threading.xml", + "ref/dotnet/zh-hans/System.Threading.xml", + "ref/dotnet/zh-hant/System.Threading.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._" + ] + }, + "System.Threading.Overlapped/4.0.0": { + "sha512": "X5LuQFhM5FTqaez3eXKJ9CbfSGZ7wj6j4hSVtxct3zmwQXLqG95qoWdvILcgN7xtrDOBIFtpiyDg0vmoI0jE2A==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Threading.Overlapped.dll", + "lib/net46/System.Threading.Overlapped.dll", + "lib/netcore50/System.Threading.Overlapped.dll", + "package/services/metadata/core-properties/e9846a81e829434aafa4ae2e8c3517d7.psmdcp", + "ref/dotnet/de/System.Threading.Overlapped.xml", + "ref/dotnet/es/System.Threading.Overlapped.xml", + "ref/dotnet/fr/System.Threading.Overlapped.xml", + "ref/dotnet/it/System.Threading.Overlapped.xml", + "ref/dotnet/ja/System.Threading.Overlapped.xml", + "ref/dotnet/ko/System.Threading.Overlapped.xml", + "ref/dotnet/ru/System.Threading.Overlapped.xml", + "ref/dotnet/System.Threading.Overlapped.dll", + "ref/dotnet/System.Threading.Overlapped.xml", + "ref/dotnet/zh-hans/System.Threading.Overlapped.xml", + "ref/dotnet/zh-hant/System.Threading.Overlapped.xml", + "ref/net46/System.Threading.Overlapped.dll", + "System.Threading.Overlapped.nuspec" + ] + }, + "System.Threading.Tasks/4.0.10": { + "sha512": "NOwJGDfk79jR0bnzosbXLVD/PdI8KzBeESoa3CofEM5v9R5EBfcI0Jyf18stx+0IYV9okmDIDxVtxq9TbnR9bQ==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/DNXCore50/System.Threading.Tasks.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Threading.Tasks.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "package/services/metadata/core-properties/a4ed35f8764a4b68bb39ec8d13b3e730.psmdcp", + "ref/dotnet/de/System.Threading.Tasks.xml", + "ref/dotnet/es/System.Threading.Tasks.xml", + "ref/dotnet/fr/System.Threading.Tasks.xml", + "ref/dotnet/it/System.Threading.Tasks.xml", + "ref/dotnet/ja/System.Threading.Tasks.xml", + "ref/dotnet/ko/System.Threading.Tasks.xml", + "ref/dotnet/ru/System.Threading.Tasks.xml", + "ref/dotnet/System.Threading.Tasks.dll", + "ref/dotnet/System.Threading.Tasks.xml", + "ref/dotnet/zh-hans/System.Threading.Tasks.xml", + "ref/dotnet/zh-hant/System.Threading.Tasks.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Threading.Tasks.dll", + "System.Threading.Tasks.nuspec" + ] + }, + "System.Threading.Tasks/4.0.11-beta-23413": { + "serviceable": true, + "sha512": "ldgZnurdbaOzzVvzm9BaZ1h7+EeRYtKR7FfXlHNWUzabHCzO0wEwINTLRc9XyMwl5i3niKos+oyGS8d9rn8+ZQ==", + "files": [ + "System.Threading.Tasks.4.0.11-beta-23413.nupkg", + "System.Threading.Tasks.4.0.11-beta-23413.nupkg.sha512", + "System.Threading.Tasks.nuspec", + "lib/DNXCore50/System.Threading.Tasks.dll", + "lib/MonoAndroid10/_._", + "lib/MonoTouch10/_._", + "lib/net46/_._", + "lib/netcore50/System.Threading.Tasks.dll", + "lib/xamarinios10/_._", + "lib/xamarinmac20/_._", + "ref/dotnet/System.Threading.Tasks.dll", + "ref/dotnet/System.Threading.Tasks.xml", + "ref/dotnet/de/System.Threading.Tasks.xml", + "ref/dotnet/es/System.Threading.Tasks.xml", + "ref/dotnet/fr/System.Threading.Tasks.xml", + "ref/dotnet/it/System.Threading.Tasks.xml", + "ref/dotnet/ja/System.Threading.Tasks.xml", + "ref/dotnet/ko/System.Threading.Tasks.xml", + "ref/dotnet/ru/System.Threading.Tasks.xml", + "ref/dotnet/zh-hans/System.Threading.Tasks.xml", + "ref/dotnet/zh-hant/System.Threading.Tasks.xml", + "ref/MonoAndroid10/_._", + "ref/MonoTouch10/_._", + "ref/net46/_._", + "ref/xamarinios10/_._", + "ref/xamarinmac20/_._", + "runtimes/win8-aot/lib/netcore50/System.Threading.Tasks.dll" + ] + }, + "xunit/2.0.0-beta5-build2785": { + "sha512": "bNycxZe/83M7o7j0IbGp3/daiPLi17hZgYZB6xttgCVDnxgAuw/TP1zetiUTW1yVUPASnAjlkBeJayv/G2Crsw==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "package/services/metadata/core-properties/6c890516e9584d3ab4031ef8a539ff70.psmdcp", + "xunit.nuspec" + ] + }, + "xunit.abstractions/2.0.0-beta5-build2785": { + "sha512": "b2RCnV2/wilCH1dsh7bAF82Ou+Vs+WfYLEItzRUUbD3YHNODx0ncpQwLz1JYjL/voBFc5mQh77hxIbgCGmyxKA==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/net35/xunit.abstractions.dll", + "lib/net35/xunit.abstractions.xml", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.abstractions.dll", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.abstractions.xml", + "package/services/metadata/core-properties/399f3731609f4d74afacd3835adec3ef.psmdcp", + "xunit.abstractions.nuspec" + ] + }, + "xunit.assert/2.0.0-beta5-build2785": { + "sha512": "1PTLrP+jLzSIhqO3JzzgPcMPmSyBlFadpFH6v3d2Vm08x/bIYHnF78h20qt6wk/t3wMu7smOsyoNcUFeP2ZnGg==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monoandroid+monotouch10/xunit.assert.dll", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monoandroid+monotouch10/xunit.assert.pdb", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monoandroid+monotouch10/xunit.assert.xml", + "package/services/metadata/core-properties/9744844535724f06a45b33a9b7fe9d6b.psmdcp", + "xunit.assert.nuspec" + ] + }, + "xunit.core/2.0.0-beta5-build2785": { + "sha512": "3rDhbyvCS1iA20A7uXxYvw3LLa4MtyXUX9Y6i3QjY/dRhIHEZcSxBjBfGP3MjPm900WnsBx2H0ryQo2RWABhhg==", + "files": [ + "[Content_Types].xml", + "_rels/.rels", + "build/monoandroid/xunit.core.props", + "build/monoandroid/xunit.execution.dll", + "build/monotouch/xunit.core.props", + "build/monotouch/xunit.execution.dll", + "build/portable-net45+win+wpa81+wp80+monotouch+monoandroid/xunit.core.props", + "build/portable-net45+win+wpa81+wp80+monotouch+monoandroid/xunit.execution.dll", + "build/win8/device/xunit.execution.dll", + "build/win8/xunit.core.props", + "build/win8/xunit.core.targets", + "build/win8/xunit.execution.dll", + "build/wp8/device/xunit.execution.dll", + "build/wp8/xunit.core.props", + "build/wp8/xunit.core.targets", + "build/wp8/xunit.execution.dll", + "build/wpa81/device/xunit.execution.dll", + "build/wpa81/device/xunit.execution.pri", + "build/wpa81/xunit.core.props", + "build/wpa81/xunit.core.targets", + "build/wpa81/xunit.execution.dll", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.core.dll", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.core.dll.tdnet", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.core.pdb", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.core.xml", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.runner.tdnet.dll", + "lib/portable-net45+aspnetcore50+win+wpa81+wp80+monotouch+monoandroid/xunit.runner.utility.dll", + "package/services/metadata/core-properties/7e3ae190162d4ceaa2151619ccd704ef.psmdcp", + "xunit.core.nuspec" + ] + } + }, + "projectFileDependencyGroups": { + "": [ + "System.Collections >= 4.0.10-*", + "System.Console >= 4.0.0-*", + "System.Diagnostics.Debug >= 4.0.10-*", + "System.Diagnostics.Tools >= 4.0.1-beta-*", + "System.IO >= 4.0.10", + "System.IO.FileSystem >= 4.0.0", + "System.IO.Pipes >= 4.0.0-beta-*", + "System.Linq >= 4.0.0-*", + "System.Reflection.Primitives >= 4.0.0", + "System.Resources.ResourceManager >= 4.0.0-*", + "System.Runtime >= 4.0.20", + "System.Runtime.Extensions >= 4.0.10", + "System.Runtime.InteropServices >= 4.0.20-*", + "System.Threading >= 4.0.10", + "xunit >= 2.0.0-beta5-build2785", + "Microsoft.NETCore.Targets >= 1.0.1-beta-*" + ], + "DNXCore,Version=v5.0": [] + } +} \ No newline at end of file