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