Imported Upstream version 5.16.0.100

Former-commit-id: 38faa55fb9669e35e7d8448b15c25dc447f25767
This commit is contained in:
Xamarin Public Jenkins (auto-signing)
2018-08-07 15:19:03 +00:00
parent 0a9828183b
commit 7d7f676260
4419 changed files with 170950 additions and 90273 deletions

View File

@@ -2,22 +2,22 @@ Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio 14
VisualStudioVersion = 14.0.25420.1
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Threading.Channels.Tests", "tests\System.Threading.Channels.Tests.csproj", "{9E984EB2-827E-4029-9647-FB5F8B67C553}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Threading.Channels.Tests", "tests\System.Threading.Channels.Tests.csproj", "{1AF01469-DBFC-4BA1-9331-8E39AA639FEE}"
ProjectSection(ProjectDependencies) = postProject
{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} = {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}
{AAADA5D3-CF64-4E9D-943C-EFDC006D6366} = {AAADA5D3-CF64-4E9D-943C-EFDC006D6366}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Threading.Channels.Performance.Tests", "tests\Performance\System.Threading.Channels.Performance.Tests.csproj", "{11ABE2F8-4FB9-48AC-91AA-D04503059550}"
ProjectSection(ProjectDependencies) = postProject
{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} = {1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}
{AAADA5D3-CF64-4E9D-943C-EFDC006D6366} = {AAADA5D3-CF64-4E9D-943C-EFDC006D6366}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Threading.Channels", "src\System.Threading.Channels.csproj", "{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Threading.Channels", "src\System.Threading.Channels.csproj", "{AAADA5D3-CF64-4E9D-943C-EFDC006D6366}"
ProjectSection(ProjectDependencies) = postProject
{9C524CA0-92FF-437B-B568-BCE8A794A69A} = {9C524CA0-92FF-437B-B568-BCE8A794A69A}
{97DB4782-7AB3-4F4C-B716-CF722A0E6066} = {97DB4782-7AB3-4F4C-B716-CF722A0E6066}
EndProjectSection
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Threading.Channels", "ref\System.Threading.Channels.csproj", "{9C524CA0-92FF-437B-B568-BCE8A794A69A}"
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "System.Threading.Channels", "ref\System.Threading.Channels.csproj", "{97DB4782-7AB3-4F4C-B716-CF722A0E6066}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{1A2F9F4A-A032-433E-B914-ADD5992BB178}"
EndProject
@@ -31,30 +31,30 @@ Global
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{9E984EB2-827E-4029-9647-FB5F8B67C553}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU
{9E984EB2-827E-4029-9647-FB5F8B67C553}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
{9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{9E984EB2-827E-4029-9647-FB5F8B67C553}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
{1AF01469-DBFC-4BA1-9331-8E39AA639FEE}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU
{1AF01469-DBFC-4BA1-9331-8E39AA639FEE}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
{1AF01469-DBFC-4BA1-9331-8E39AA639FEE}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{1AF01469-DBFC-4BA1-9331-8E39AA639FEE}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
{11ABE2F8-4FB9-48AC-91AA-D04503059550}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{11ABE2F8-4FB9-48AC-91AA-D04503059550}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{11ABE2F8-4FB9-48AC-91AA-D04503059550}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{11ABE2F8-4FB9-48AC-91AA-D04503059550}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU
{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
{9C524CA0-92FF-437B-B568-BCE8A794A69A}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU
{9C524CA0-92FF-437B-B568-BCE8A794A69A}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
{9C524CA0-92FF-437B-B568-BCE8A794A69A}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{9C524CA0-92FF-437B-B568-BCE8A794A69A}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
{AAADA5D3-CF64-4E9D-943C-EFDC006D6366}.Debug|Any CPU.ActiveCfg = netcoreapp-Debug|Any CPU
{AAADA5D3-CF64-4E9D-943C-EFDC006D6366}.Debug|Any CPU.Build.0 = netcoreapp-Debug|Any CPU
{AAADA5D3-CF64-4E9D-943C-EFDC006D6366}.Release|Any CPU.ActiveCfg = netcoreapp-Release|Any CPU
{AAADA5D3-CF64-4E9D-943C-EFDC006D6366}.Release|Any CPU.Build.0 = netcoreapp-Release|Any CPU
{97DB4782-7AB3-4F4C-B716-CF722A0E6066}.Debug|Any CPU.ActiveCfg = netstandard-Debug|Any CPU
{97DB4782-7AB3-4F4C-B716-CF722A0E6066}.Debug|Any CPU.Build.0 = netstandard-Debug|Any CPU
{97DB4782-7AB3-4F4C-B716-CF722A0E6066}.Release|Any CPU.ActiveCfg = netstandard-Release|Any CPU
{97DB4782-7AB3-4F4C-B716-CF722A0E6066}.Release|Any CPU.Build.0 = netstandard-Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(NestedProjects) = preSolution
{9E984EB2-827E-4029-9647-FB5F8B67C553} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
{1AF01469-DBFC-4BA1-9331-8E39AA639FEE} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
{11ABE2F8-4FB9-48AC-91AA-D04503059550} = {1A2F9F4A-A032-433E-B914-ADD5992BB178}
{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD}
{9C524CA0-92FF-437B-B568-BCE8A794A69A} = {2E666815-2EDB-464B-9DF6-380BF4789AD4}
{AAADA5D3-CF64-4E9D-943C-EFDC006D6366} = {E107E9C1-E893-4E87-987E-04EF0DCEAEFD}
{97DB4782-7AB3-4F4C-B716-CF722A0E6066} = {2E666815-2EDB-464B-9DF6-380BF4789AD4}
EndGlobalSection
EndGlobal

View File

@@ -26,8 +26,6 @@ namespace System.Threading.Channels
public static System.Threading.Channels.Channel<T> CreateBounded<T>(System.Threading.Channels.BoundedChannelOptions options) { throw null; }
public static System.Threading.Channels.Channel<T> CreateUnbounded<T>() { throw null; }
public static System.Threading.Channels.Channel<T> CreateUnbounded<T>(System.Threading.Channels.UnboundedChannelOptions options) { throw null; }
public static System.Threading.Channels.Channel<T> CreateUnbuffered<T>() { throw null; }
public static System.Threading.Channels.Channel<T> CreateUnbuffered<T>(System.Threading.Channels.UnbufferedChannelOptions options) { throw null; }
}
public partial class ChannelClosedException : System.InvalidOperationException
{
@@ -49,7 +47,7 @@ namespace System.Threading.Channels
public virtual System.Threading.Tasks.Task Completion { get { throw null; } }
public virtual System.Threading.Tasks.ValueTask<T> ReadAsync(CancellationToken cancellationToken = default) { throw null; }
public abstract bool TryRead(out T item);
public abstract System.Threading.Tasks.Task<bool> WaitToReadAsync(System.Threading.CancellationToken cancellationToken=default);
public abstract System.Threading.Tasks.ValueTask<bool> WaitToReadAsync(System.Threading.CancellationToken cancellationToken=default);
}
public abstract partial class ChannelWriter<T>
{
@@ -57,8 +55,8 @@ namespace System.Threading.Channels
public void Complete(System.Exception error=null) { }
public virtual bool TryComplete(System.Exception error=null) { throw null; }
public abstract bool TryWrite(T item);
public abstract System.Threading.Tasks.Task<bool> WaitToWriteAsync(System.Threading.CancellationToken cancellationToken=default);
public virtual System.Threading.Tasks.Task WriteAsync(T item, System.Threading.CancellationToken cancellationToken=default) { throw null; }
public abstract System.Threading.Tasks.ValueTask<bool> WaitToWriteAsync(System.Threading.CancellationToken cancellationToken=default);
public virtual System.Threading.Tasks.ValueTask WriteAsync(T item, System.Threading.CancellationToken cancellationToken=default) { throw null; }
}
public abstract partial class Channel<T> : System.Threading.Channels.Channel<T, T>
{
@@ -76,8 +74,4 @@ namespace System.Threading.Channels
{
public UnboundedChannelOptions() { }
}
public sealed partial class UnbufferedChannelOptions : System.Threading.Channels.ChannelOptions
{
public UnbufferedChannelOptions() { }
}
}

View File

@@ -2,10 +2,12 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<ProjectGuid>{9C524CA0-92FF-437B-B568-BCE8A794A69A}</ProjectGuid>
<ProjectGuid>{97DB4782-7AB3-4F4C-B716-CF722A0E6066}</ProjectGuid>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard1.3-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard1.3-Release|AnyCPU'" />
<ItemGroup>
<Compile Include="System.Threading.Channels.cs" />
</ItemGroup>
@@ -20,4 +22,4 @@
<ProjectReference Include="..\..\System.Threading.Tasks.Extensions\ref\System.Threading.Tasks.Extensions.csproj" />
</ItemGroup>
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.targets))\dir.targets" />
</Project>
</Project>

View File

@@ -4,6 +4,7 @@
<BuildConfigurations>
netstandard1.3;
netstandard;
netcoreapp;
</BuildConfigurations>
</PropertyGroup>
</Project>

View File

@@ -120,4 +120,13 @@
<data name="ChannelClosedException_DefaultMessage" xml:space="preserve">
<value>The channel has been closed.</value>
</data>
</root>
<data name="InvalidOperation_IncompleteAsyncOperation" xml:space="preserve">
<value>The asynchronous operation has not completed.</value>
</data>
<data name="InvalidOperation_MultipleContinuations" xml:space="preserve">
<value>Another continuation was already registered.</value>
</data>
<data name="InvalidOperation_IncorrectToken" xml:space="preserve">
<value>The result of the operation was already consumed and may not be used again.</value>
</data>
</root>

View File

@@ -2,12 +2,16 @@
<Project ToolsVersion="15.0" DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<Import Project="$([MSBuild]::GetDirectoryNameOfFileAbove($(MSBuildThisFileDirectory), dir.props))\dir.props" />
<PropertyGroup>
<ProjectGuid>{1032D5F6-5AE7-4002-A0E4-FEBEADFEA977}</ProjectGuid>
<ProjectGuid>{AAADA5D3-CF64-4E9D-943C-EFDC006D6366}</ProjectGuid>
<RootNamespace>System.Threading.Channels</RootNamespace>
<DocumentationFile>$(OutputPath)$(MSBuildProjectName).xml</DocumentationFile>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netcoreapp-Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard-Release|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard1.3-Debug|AnyCPU'" />
<PropertyGroup Condition="'$(Configuration)|$(Platform)' == 'netstandard1.3-Release|AnyCPU'" />
<ItemGroup>
<Compile Include="System\VoidResult.cs" />
<Compile Include="System\Collections\Generic\Dequeue.cs" />
@@ -22,10 +26,9 @@
<Compile Include="System\Threading\Channels\Channel_1.cs" />
<Compile Include="System\Threading\Channels\Channel_2.cs" />
<Compile Include="System\Threading\Channels\IDebugEnumerator.cs" />
<Compile Include="System\Threading\Channels\Interactor.cs" />
<Compile Include="System\Threading\Channels\AsyncOperation.cs" />
<Compile Include="System\Threading\Channels\SingleConsumerUnboundedChannel.cs" />
<Compile Include="System\Threading\Channels\UnboundedChannel.cs" />
<Compile Include="System\Threading\Channels\UnbufferedChannel.cs" />
<Compile Include="$(CommonPath)\System\Collections\Concurrent\SingleProducerConsumerQueue.cs">
<Link>Common\System\Collections\Concurrent\SingleProducerConsumerQueue.cs</Link>
</Compile>

View File

@@ -0,0 +1,449 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Diagnostics;
using System.Runtime.ExceptionServices;
using System.Threading.Tasks;
using System.Threading.Tasks.Sources;
namespace System.Threading.Channels
{
internal abstract class AsyncOperation
{
/// <summary>Sentinel object used in a field to indicate the operation is available for use.</summary>
protected static readonly Action<object> s_availableSentinel = new Action<object>(s => Debug.Fail($"{nameof(AsyncOperation)}.{nameof(s_availableSentinel)} invoked with {s}."));
/// <summary>Sentinel object used in a field to indicate the operation has completed.</summary>
protected static readonly Action<object> s_completedSentinel = new Action<object>(s => Debug.Fail($"{nameof(AsyncOperation)}.{nameof(s_completedSentinel)} invoked with {s}"));
/// <summary>Throws an exception indicating that the operation's result was accessed before the operation completed.</summary>
protected static void ThrowIncompleteOperationException() =>
throw new InvalidOperationException(SR.InvalidOperation_IncompleteAsyncOperation);
/// <summary>Throws an exception indicating that multiple continuations can't be set for the same operation.</summary>
protected static void ThrowMultipleContinuations() =>
throw new InvalidOperationException(SR.InvalidOperation_MultipleContinuations);
/// <summary>Throws an exception indicating that the operation was used after it was supposed to be used.</summary>
protected static void ThrowIncorrectCurrentIdException() =>
throw new InvalidOperationException(SR.InvalidOperation_IncorrectToken);
}
/// <summary>The representation of an asynchronous operation that has a result value.</summary>
/// <typeparam name="TResult">Specifies the type of the result. May be <see cref="VoidResult"/>.</typeparam>
internal class AsyncOperation<TResult> : AsyncOperation, IValueTaskSource, IValueTaskSource<TResult>
{
/// <summary>Registration with a provided cancellation token.</summary>
private readonly CancellationTokenRegistration _registration;
/// <summary>true if this object is pooled and reused; otherwise, false.</summary>
/// <remarks>
/// If the operation is cancelable, then it can't be pooled. And if it's poolable, there must never be race conditions to complete it,
/// which is the main reason poolable objects can't be cancelable, as then cancellation could fire, the object could get reused,
/// and then we may end up trying to complete an object that's used by someone else.
/// </remarks>
private readonly bool _pooled;
/// <summary>Whether continuations should be forced to run asynchronously.</summary>
private readonly bool _runContinuationsAsynchronously;
/// <summary>Only relevant to cancelable operations; 0 if the operation hasn't had completion reserved, 1 if it has.</summary>
private volatile int _completionReserved = 0;
/// <summary>The result of the operation.</summary>
private TResult _result;
/// <summary>Any error that occurred during the operation.</summary>
private ExceptionDispatchInfo _error;
/// <summary>The continuation callback.</summary>
/// <remarks>
/// This may be the completion sentinel if the operation has already completed.
/// This may be the available sentinel if the operation is being pooled and is available for use.
/// This may be null if the operation is pending.
/// This may be another callback if the operation has had a callback hooked up with OnCompleted.
/// </remarks>
private Action<object> _continuation;
/// <summary>State object to be passed to <see cref="_continuation"/>.</summary>
private object _continuationState;
/// <summary>Scheduling context (a <see cref="SynchronizationContext"/> or <see cref="TaskScheduler"/>) to which to queue the continuation. May be null.</summary>
private object _schedulingContext;
/// <summary>Execution context to use when invoking <see cref="_continuation"/>. May be null.</summary>
private ExecutionContext _executionContext;
/// <summary>The token value associated with the current operation.</summary>
/// <remarks>
/// IValueTaskSource operations on this instance are only valid if the provided token matches this value,
/// which is incremented once GetResult is called to avoid multiple awaits on the same instance.
/// </remarks>
private short _currentId;
/// <summary>Initializes the interactor.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
/// <param name="cancellationToken">The cancellation token used to cancel the operation.</param>
/// <param name="pooled">Whether this instance is pooled and reused.</param>
public AsyncOperation(bool runContinuationsAsynchronously, CancellationToken cancellationToken = default, bool pooled = false)
{
_continuation = pooled ? s_availableSentinel : null;
_pooled = pooled;
_runContinuationsAsynchronously = runContinuationsAsynchronously;
if (cancellationToken.CanBeCanceled)
{
Debug.Assert(!_pooled, "Cancelable operations can't be pooled");
CancellationToken = cancellationToken;
_registration = cancellationToken.Register(s =>
{
var thisRef = (AsyncOperation<TResult>)s;
thisRef.TrySetCanceled(thisRef.CancellationToken);
}, this);
}
}
/// <summary>Gets or sets the next operation in the linked list of operations.</summary>
public AsyncOperation<TResult> Next { get; set; }
/// <summary>Gets the cancellation token associated with this operation.</summary>
public CancellationToken CancellationToken { get; }
/// <summary>Gets a <see cref="ValueTask"/> backed by this instance and its current token.</summary>
public ValueTask ValueTask => new ValueTask(this, _currentId);
/// <summary>Gets a <see cref="ValueTask{TResult}"/> backed by this instance and its current token.</summary>
public ValueTask<TResult> ValueTaskOfT => new ValueTask<TResult>(this, _currentId);
/// <summary>Gets the current status of the operation.</summary>
/// <param name="token">The token that must match <see cref="_currentId"/>.</param>
public ValueTaskSourceStatus GetStatus(short token)
{
if (_currentId == token)
{
return
!IsCompleted ? ValueTaskSourceStatus.Pending :
_error == null ? ValueTaskSourceStatus.Succeeded :
_error.SourceException is OperationCanceledException ? ValueTaskSourceStatus.Canceled :
ValueTaskSourceStatus.Faulted;
}
ThrowIncorrectCurrentIdException();
return default; // just to satisfy compiler
}
/// <summary>Gets whether the operation has completed.</summary>
/// <remarks>
/// The operation is considered completed if both a) it's in the completed state,
/// AND b) it has a non-null continuation. We need to consider both because they're
/// not set atomically. If we only considered the state, then if we set the state to
/// completed and then set the continuation, it's possible for an awaiter to check
/// IsCompleted, see true, call GetResult, and return the object to the pool, and only
/// then do we try to store the continuation into an object we no longer own. If we
/// only considered the state, then if we set the continuation and then set the state,
/// a racing awaiter could see the continuation set before the state has transitioned
/// to completed and could end up calling GetResult in an incomplete state. And if we
/// only considered the continuation, then we have issues if OnCompleted is used before
/// the operation completes, as the continuation will be
/// </remarks>
internal bool IsCompleted => ReferenceEquals(_continuation, s_completedSentinel);
/// <summary>Gets the result of the operation.</summary>
/// <param name="token">The token that must match <see cref="_currentId"/>.</param>
public TResult GetResult(short token)
{
if (_currentId != token)
{
ThrowIncorrectCurrentIdException();
}
if (!IsCompleted)
{
ThrowIncompleteOperationException();
}
ExceptionDispatchInfo error = _error;
TResult result = _result;
_currentId++;
if (_pooled)
{
Volatile.Write(ref _continuation, s_availableSentinel); // only after fetching all needed data
}
error?.Throw();
return result;
}
/// <summary>Gets the result of the operation.</summary>
/// <param name="token">The token that must match <see cref="_currentId"/>.</param>
void IValueTaskSource.GetResult(short token)
{
if (_currentId != token)
{
ThrowIncorrectCurrentIdException();
}
if (!IsCompleted)
{
ThrowIncompleteOperationException();
}
ExceptionDispatchInfo error = _error;
_currentId++;
if (_pooled)
{
Volatile.Write(ref _continuation, s_availableSentinel); // only after fetching all needed data
}
error?.Throw();
}
/// <summary>Attempts to take ownership of the pooled instance.</summary>
/// <returns>true if the instance is now owned by the caller, in which case its state has been reset; otherwise, false.</returns>
public bool TryOwnAndReset()
{
Debug.Assert(_pooled, "Should only be used for pooled objects");
if (ReferenceEquals(Interlocked.CompareExchange(ref _continuation, null, s_availableSentinel), s_availableSentinel))
{
_continuationState = null;
_result = default;
_error = null;
_schedulingContext = null;
_executionContext = null;
return true;
}
return false;
}
/// <summary>Hooks up a continuation callback for when the operation has completed.</summary>
/// <param name="continuation">The callback.</param>
/// <param name="state">The state to pass to the callback.</param>
/// <param name="token">The current token that must match <see cref="_currentId"/>.</param>
/// <param name="flags">Flags that influence the behavior of the callback.</param>
public void OnCompleted(Action<object> continuation, object state, short token, ValueTaskSourceOnCompletedFlags flags)
{
if (_currentId != token)
{
ThrowIncorrectCurrentIdException();
}
// We need to store the state before the CompareExchange, so that if it completes immediately
// after the CompareExchange, it'll find the state already stored. If someone misuses this
// and schedules multiple continuations erroneously, we could end up using the wrong state.
// Make a best-effort attempt to catch such misuse.
if (_continuationState != null)
{
ThrowMultipleContinuations();
}
_continuationState = state;
// Capture the execution context if necessary.
Debug.Assert(_executionContext == null);
if ((flags & ValueTaskSourceOnCompletedFlags.FlowExecutionContext) != 0)
{
_executionContext = ExecutionContext.Capture();
}
// Capture the scheduling context if necessary.
Debug.Assert(_schedulingContext == null);
SynchronizationContext sc = null;
TaskScheduler ts = null;
if ((flags & ValueTaskSourceOnCompletedFlags.UseSchedulingContext) != 0)
{
sc = SynchronizationContext.Current;
if (sc != null && sc.GetType() != typeof(SynchronizationContext))
{
_schedulingContext = sc;
}
else
{
ts = TaskScheduler.Current;
if (ts != TaskScheduler.Default)
{
_schedulingContext = ts;
}
}
}
// Try to set the provided continuation into _continuation. If this succeeds, that means the operation
// has not yet completed, and the completer will be responsible for invoking the callback. If this fails,
// that means the operation has already completed, and we must invoke the callback, but because we're still
// inside the awaiter's OnCompleted method and we want to avoid possible stack dives, we must invoke
// the continuation asynchronously rather than synchronously.
Action<object> prevContinuation = Interlocked.CompareExchange(ref _continuation, continuation, null);
if (prevContinuation != null)
{
// If the set failed because there's already a delegate in _continuation, but that delegate is
// something other than s_completedSentinel, something went wrong, which should only happen if
// the instance was erroneously used, likely to hook up multiple continuations.
Debug.Assert(IsCompleted, $"Expected IsCompleted");
if (!ReferenceEquals(prevContinuation, s_completedSentinel))
{
Debug.Assert(prevContinuation != s_availableSentinel, "Continuation was the available sentinel.");
ThrowMultipleContinuations();
}
// Queue the continuation.
if (sc != null)
{
sc.Post(s =>
{
var t = (Tuple<Action<object>, object>)s;
t.Item1(t.Item2);
}, Tuple.Create(continuation, state));
}
else
{
Task.Factory.StartNew(continuation, state, CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts ?? TaskScheduler.Default);
}
}
}
/// <summary>Unregisters from cancellation.</summary>
/// <remarks>
/// This is important for two reasons:
/// 1. To avoid leaking a registration into a token, so it must be done prior to completing the operation.
/// 2. To avoid having to worry about concurrent completion; once invoked, the caller can be guaranteed
/// that no one else will try to complete the operation (assuming the caller is properly constructed
/// and themselves guarantees only a single completer other than through cancellation).
/// </remarks>
public void UnregisterCancellation() => _registration.Dispose();
/// <summary>Completes the operation with a success state and the specified result.</summary>
/// <param name="item">The result value.</param>
/// <returns>true if the operation could be successfully transitioned to a completed state; false if it was already completed.</returns>
public bool TrySetResult(TResult item)
{
UnregisterCancellation();
if (TryReserveCompletionIfCancelable())
{
_result = item;
SignalCompletion();
return true;
}
return false;
}
/// <summary>Completes the operation with a failed state and the specified error.</summary>
/// <param name="exception">The error.</param>
/// <returns>true if the operation could be successfully transitioned to a completed state; false if it was already completed.</returns>
public bool TrySetException(Exception exception)
{
UnregisterCancellation();
if (TryReserveCompletionIfCancelable())
{
_error = ExceptionDispatchInfo.Capture(exception);
SignalCompletion();
return true;
}
return false;
}
/// <summary>Completes the operation with a failed state and a cancellation error.</summary>
/// <param name="cancellationToken">The cancellation token that caused the cancellation.</param>
/// <returns>true if the operation could be successfully transitioned to a completed state; false if it was already completed.</returns>
public bool TrySetCanceled(CancellationToken cancellationToken = default)
{
if (TryReserveCompletionIfCancelable())
{
_error = ExceptionDispatchInfo.Capture(new OperationCanceledException(cancellationToken));
SignalCompletion();
return true;
}
return false;
}
/// <summary>Attempts to reserve this instance for completion.</summary>
/// <remarks>
/// This will always return true for non-cancelable objects, as they only ever have a single owner
/// responsible for completion. For cancelable operations, this will attempt to atomically transition
/// from Initialized to CompletionReserved.
/// </remarks>
private bool TryReserveCompletionIfCancelable() =>
!CancellationToken.CanBeCanceled ||
Interlocked.CompareExchange(ref _completionReserved, 1, 0) == 0;
/// <summary>Signals to a registered continuation that the operation has now completed.</summary>
private void SignalCompletion()
{
if (_continuation != null || Interlocked.CompareExchange(ref _continuation, s_completedSentinel, null) != null)
{
ExecutionContext ec = _executionContext;
if (ec != null)
{
ExecutionContext.Run(ec, s => ((AsyncOperation<TResult>)s).SignalCompletionCore(), this);
}
else
{
SignalCompletionCore();
}
}
}
/// <summary>Invokes the registered continuation; separated out of SignalCompletion for convenience so that it may be invoked on multiple code paths.</summary>
private void SignalCompletionCore()
{
Debug.Assert(_continuation != s_completedSentinel, $"The continuation was the completion sentinel.");
Debug.Assert(_continuation != s_availableSentinel, $"The continuation was the available sentinel.");
if (_schedulingContext == null)
{
// There's no captured scheduling context. If we're forced to run continuations asynchronously, queue it.
// Otherwise fall through to invoke it synchronously.
if (_runContinuationsAsynchronously)
{
Task.Factory.StartNew(s => ((AsyncOperation<TResult>)s).SetCompletionAndInvokeContinuation(), this,
CancellationToken.None, TaskCreationOptions.DenyChildAttach, TaskScheduler.Default);
return;
}
}
else if (_schedulingContext is SynchronizationContext sc)
{
// There's a captured synchronization context. If we're forced to run continuations asynchronously,
// or if there's a current synchronization context that's not the one we're targeting, queue it.
// Otherwise fall through to invoke it synchronously.
if (_runContinuationsAsynchronously || sc != SynchronizationContext.Current)
{
sc.Post(s => ((AsyncOperation<TResult>)s).SetCompletionAndInvokeContinuation(), this);
return;
}
}
else
{
// There's a captured TaskScheduler. If we're forced to run continuations asynchronously,
// or if there's a current scheduler that's not the one we're targeting, queue it.
// Otherwise fall through to invoke it synchronously.
TaskScheduler ts = (TaskScheduler)_schedulingContext;
Debug.Assert(ts != null, "Expected a TaskScheduler");
if (_runContinuationsAsynchronously || ts != TaskScheduler.Current)
{
Task.Factory.StartNew(s => ((AsyncOperation<TResult>)s).SetCompletionAndInvokeContinuation(), this,
CancellationToken.None, TaskCreationOptions.DenyChildAttach, ts);
return;
}
}
// Invoke the continuation synchronously.
SetCompletionAndInvokeContinuation();
}
private void SetCompletionAndInvokeContinuation()
{
Action<object> c = _continuation;
_continuation = s_completedSentinel;
c(_continuationState);
}
}
/// <summary>The representation of an asynchronous operation that has a result value and carries additional data with it.</summary>
/// <typeparam name="TData">Specifies the type of data being written.</typeparam>
internal sealed class VoidAsyncOperationWithData<TData> : AsyncOperation<VoidResult>
{
/// <summary>Initializes the interactor.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
/// <param name="cancellationToken">The cancellation token used to cancel the operation.</param>
/// <param name="pooled">Whether this instance is pooled and reused.</param>
public VoidAsyncOperationWithData(bool runContinuationsAsynchronously, CancellationToken cancellationToken = default, bool pooled = false) :
base(runContinuationsAsynchronously, cancellationToken, pooled)
{
}
/// <summary>The item being written.</summary>
public TData Item { get; set; }
}
}

View File

@@ -52,25 +52,5 @@ namespace System.Threading.Channels
return new BoundedChannel<T>(options.Capacity, options.FullMode, !options.AllowSynchronousContinuations);
}
/// <summary>Creates a channel that doesn't buffer any items.</summary>
/// <typeparam name="T">Specifies the type of data in the channel.</typeparam>
/// <returns>The created channel.</returns>
public static Channel<T> CreateUnbuffered<T>() =>
new UnbufferedChannel<T>();
/// <summary>Creates a channel that doesn't buffer any items.</summary>
/// <typeparam name="T">Specifies the type of data in the channel.</typeparam>
/// <param name="options">Options that guide the behavior of the channel.</param>
/// <returns>The created channel.</returns>
public static Channel<T> CreateUnbuffered<T>(UnbufferedChannelOptions options)
{
if (options == null)
{
throw new ArgumentNullException(nameof(options));
}
return new UnbufferedChannel<T>();
}
}
}

View File

@@ -99,9 +99,4 @@ namespace System.Threading.Channels
public sealed class UnboundedChannelOptions : ChannelOptions
{
}
/// <summary>Provides options that control the behavior of <see cref="UnbufferedChannel{T}"/> instances.</summary>
public sealed class UnbufferedChannelOptions : ChannelOptions
{
}
}

View File

@@ -29,10 +29,10 @@ namespace System.Threading.Channels
/// A <see cref="Task{Boolean}"/> that will complete with a <c>true</c> result when data is available to read
/// or with a <c>false</c> result when no further data will ever be available to be read.
/// </returns>
public abstract Task<bool> WaitToReadAsync(CancellationToken cancellationToken = default);
public abstract ValueTask<bool> WaitToReadAsync(CancellationToken cancellationToken = default);
/// <summary>Asynchronously reads an item from the channel.</summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the read operation.</param>
/// <summary>Asynchronously reads an item from the channel.</summary>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the read operation.</param>
/// <returns>A <see cref="ValueTask{TResult}"/> that represents the asynchronous read operation.</returns>
public virtual ValueTask<T> ReadAsync(CancellationToken cancellationToken = default)
{
@@ -57,24 +57,17 @@ namespace System.Threading.Channels
async ValueTask<T> ReadAsyncCore(CancellationToken ct)
{
try
while (true)
{
while (true)
if (!await WaitToReadAsync(ct).ConfigureAwait(false))
{
if (!await WaitToReadAsync(ct))
{
throw new ChannelClosedException();
}
if (TryRead(out T item))
{
return item;
}
throw new ChannelClosedException();
}
if (TryRead(out T item))
{
return item;
}
}
catch (Exception exc) when (!(exc is ChannelClosedException || exc is OperationCanceledException))
{
throw new ChannelClosedException(exc);
}
}
}

View File

@@ -23,7 +23,7 @@ namespace System.Threading.Channels
/// <summary>Completes the specified TaskCompletionSource.</summary>
/// <param name="tcs">The source to complete.</param>
/// <param name="error">
/// The optional exception with which to complete.
/// The optional exception with which to complete.
/// If this is null or the DoneWritingSentinel, 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.
@@ -44,74 +44,71 @@ namespace System.Threading.Channels
}
}
/// <summary>Wake up all of the waiters and null out the field.</summary>
/// <param name="waiters">The waiters.</param>
/// <param name="result">The value with which to complete each waiter.</param>
internal static void WakeUpWaiters(ref ReaderInteractor<bool> waiters, bool result)
{
ReaderInteractor<bool> w = waiters;
if (w != null)
{
w.Success(result);
waiters = null;
}
}
/// <summary>Wake up all of the waiters and null out the field.</summary>
/// <param name="waiters">The waiters.</param>
/// <param name="result">The success value with which to complete each waiter if <paramref name="error">error</paramref> is null.</param>
/// <param name="error">The failure with which to cmplete each waiter, if non-null.</param>
internal static void WakeUpWaiters(ref ReaderInteractor<bool> waiters, bool result, Exception error = null)
{
ReaderInteractor<bool> w = waiters;
if (w != null)
{
if (error != null)
{
w.Fail(error);
}
else
{
w.Success(result);
}
waiters = null;
}
}
/// <summary>Removes all interactors from the queue, failing each.</summary>
/// <param name="interactors">The queue of interactors to complete.</param>
/// <param name="error">The error with which to complete each interactor.</param>
internal static void FailInteractors<T, TInner>(Dequeue<T> interactors, Exception error) where T : Interactor<TInner>
/// <summary>Gets a value task representing an error.</summary>
/// <typeparam name="T">Specifies the type of the value that would have been returned.</typeparam>
/// <param name="error">The error. This may be <see cref="s_doneWritingSentinel"/>.</param>
/// <returns>The failed task.</returns>
internal static ValueTask<T> GetInvalidCompletionValueTask<T>(Exception error)
{
Debug.Assert(error != null);
while (!interactors.IsEmpty)
Task<T> t =
error == s_doneWritingSentinel ? Task.FromException<T>(CreateInvalidCompletionException()) :
error is OperationCanceledException oce ? Task.FromCanceled<T>(oce.CancellationToken.IsCancellationRequested ? oce.CancellationToken : new CancellationToken(true)) :
Task.FromException<T>(CreateInvalidCompletionException(error));
return new ValueTask<T>(t);
}
internal static ValueTask<bool> QueueWaiter(ref AsyncOperation<bool> tail, AsyncOperation<bool> waiter)
{
AsyncOperation<bool> c = tail;
if (c == null)
{
interactors.DequeueHead().Fail(error);
waiter.Next = waiter;
}
else
{
waiter.Next = c.Next;
c.Next = waiter;
}
tail = waiter;
return waiter.ValueTaskOfT;
}
internal static void WakeUpWaiters(ref AsyncOperation<bool> listTail, bool result, Exception error = null)
{
AsyncOperation<bool> tail = listTail;
if (tail != null)
{
listTail = null;
AsyncOperation<bool> head = tail.Next;
AsyncOperation<bool> c = head;
do
{
AsyncOperation<bool> next = c.Next;
c.Next = null;
bool completed = error != null ? c.TrySetException(error) : c.TrySetResult(result);
Debug.Assert(completed || c.CancellationToken.CanBeCanceled);
c = next;
}
while (c != head);
}
}
/// <summary>Gets or creates a "waiter" (e.g. WaitForRead/WriteAsync) interactor.</summary>
/// <param name="waiter">The field storing the waiter interactor.</param>
/// <param name="runContinuationsAsynchronously">true to force continuations to run asynchronously; otherwise, false.</param>
/// <param name="cancellationToken">The token to use to cancel the wait.</param>
internal static Task<bool> GetOrCreateWaiter(ref ReaderInteractor<bool> waiter, bool runContinuationsAsynchronously, CancellationToken cancellationToken)
/// <summary>Removes all operations from the queue, failing each.</summary>
/// <param name="operations">The queue of operations to complete.</param>
/// <param name="error">The error with which to complete each operations.</param>
internal static void FailOperations<T, TInner>(Dequeue<T> operations, Exception error) where T : AsyncOperation<TInner>
{
// Get the existing waiters interactor.
ReaderInteractor<bool> w = waiter;
// If there isn't one, create one. This explicitly does not include the cancellation token,
// as we reuse it for any number of waiters that overlap.
if (w == null)
Debug.Assert(error != null);
while (!operations.IsEmpty)
{
waiter = w = ReaderInteractor<bool>.Create(runContinuationsAsynchronously);
operations.DequeueHead().TrySetException(error);
}
// If the cancellation token can't be canceled, then just return the waiter task.
// If it can, we need to return a task that will complete when the waiter task does but that can also be canceled.
// Easiest way to do that is with a cancelable continuation.
return cancellationToken.CanBeCanceled ?
w.Task.ContinueWith(t => t.Result, cancellationToken, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default) :
w.Task;
}
/// <summary>Creates and returns an exception object to indicate that a channel has been closed.</summary>

View File

@@ -31,38 +31,38 @@ namespace System.Threading.Channels
/// A <see cref="Task{Boolean}"/> that will complete with a <c>true</c> result when space is available to write an item
/// or with a <c>false</c> result when no further writing will be permitted.
/// </returns>
public abstract Task<bool> WaitToWriteAsync(CancellationToken cancellationToken = default);
public abstract ValueTask<bool> WaitToWriteAsync(CancellationToken cancellationToken = default);
/// <summary>Asynchronously writes an item to the channel.</summary>
/// <param name="item">The value to write to the channel.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> used to cancel the write operation.</param>
/// <returns>A <see cref="Task"/> that represents the asynchronous write operation.</returns>
public virtual Task WriteAsync(T item, CancellationToken cancellationToken = default)
public virtual ValueTask WriteAsync(T item, CancellationToken cancellationToken = default)
{
try
{
return
cancellationToken.IsCancellationRequested ? Task.FromCanceled<T>(cancellationToken) :
TryWrite(item) ? Task.CompletedTask :
WriteAsyncCore(item, cancellationToken);
cancellationToken.IsCancellationRequested ? new ValueTask(Task.FromCanceled<T>(cancellationToken)) :
TryWrite(item) ? default :
new ValueTask(WriteAsyncCore(item, cancellationToken));
}
catch (Exception e)
{
return Task.FromException(e);
return new ValueTask(Task.FromException(e));
}
}
async Task WriteAsyncCore(T innerItem, CancellationToken ct)
private async Task WriteAsyncCore(T innerItem, CancellationToken ct)
{
while (await WaitToWriteAsync(ct).ConfigureAwait(false))
{
while (await WaitToWriteAsync(ct).ConfigureAwait(false))
if (TryWrite(innerItem))
{
if (TryWrite(innerItem))
{
return;
}
return;
}
throw ChannelUtilities.CreateInvalidCompletionException();
}
throw ChannelUtilities.CreateInvalidCompletionException();
}
/// <summary>Mark the channel as being complete, meaning no more items will be written to it.</summary>

View File

@@ -1,149 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
namespace System.Threading.Channels
{
/// <summary>A base class for a blocked or waiting reader or writer.</summary>
/// <typeparam name="T">Specifies the type of data passed to the reader or writer.</typeparam>
internal abstract class Interactor<T> : TaskCompletionSource<T>
{
/// <summary>Initializes the interactor.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
protected Interactor(bool runContinuationsAsynchronously) :
base(runContinuationsAsynchronously ? TaskCreationOptions.RunContinuationsAsynchronously : TaskCreationOptions.None) { }
/// <summary>Completes the interactor with a success state and the specified result.</summary>
/// <param name="item">The result value.</param>
/// <returns>true if the interactor could be successfully transitioned to a completed state; false if it was already completed.</returns>
internal bool Success(T item)
{
UnregisterCancellation();
return TrySetResult(item);
}
/// <summary>Completes the interactor with a failed state and the specified error.</summary>
/// <param name="exception">The error.</param>
/// <returns>true if the interactor could be successfully transitioned to a completed state; false if it was already completed.</returns>
internal bool Fail(Exception exception)
{
UnregisterCancellation();
return TrySetException(exception);
}
/// <summary>Unregister cancellation in case cancellation was registered.</summary>
internal virtual void UnregisterCancellation() { }
}
/// <summary>A blocked or waiting reader.</summary>
/// <typeparam name="T">Specifies the type of data being read.</typeparam>
internal class ReaderInteractor<T> : Interactor<T>
{
/// <summary>Initializes the reader.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
protected ReaderInteractor(bool runContinuationsAsynchronously) : base(runContinuationsAsynchronously) { }
/// <summary>Creates a reader.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
/// <returns>The reader.</returns>
public static ReaderInteractor<T> Create(bool runContinuationsAsynchronously) =>
new ReaderInteractor<T>(runContinuationsAsynchronously);
/// <summary>Creates a reader.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the read operation.</param>
/// <returns>The reader.</returns>
public static ReaderInteractor<T> Create(bool runContinuationsAsynchronously, CancellationToken cancellationToken) =>
cancellationToken.CanBeCanceled ?
new CancelableReaderInteractor<T>(runContinuationsAsynchronously, cancellationToken) :
new ReaderInteractor<T>(runContinuationsAsynchronously);
}
/// <summary>A blocked or waiting writer.</summary>
/// <typeparam name="T">Specifies the type of data being written.</typeparam>
internal class WriterInteractor<T> : Interactor<VoidResult>
{
/// <summary>Initializes the writer.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
protected WriterInteractor(bool runContinuationsAsynchronously) : base(runContinuationsAsynchronously) { }
/// <summary>The item being written.</summary>
internal T Item { get; private set; }
/// <summary>Creates a writer.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
/// <param name="item">The item being written.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the read operation.</param>
/// <returns>The reader.</returns>
public static WriterInteractor<T> Create(bool runContinuationsAsynchronously, T item, CancellationToken cancellationToken)
{
WriterInteractor<T> w = cancellationToken.CanBeCanceled ?
new CancelableWriter<T>(runContinuationsAsynchronously, cancellationToken) :
new WriterInteractor<T>(runContinuationsAsynchronously);
w.Item = item;
return w;
}
}
/// <summary>A blocked or waiting reader where the read can be canceled.</summary>
/// <typeparam name="T">Specifies the type of data being read.</typeparam>
internal sealed class CancelableReaderInteractor<T> : ReaderInteractor<T>
{
/// <summary>The token used for cancellation.</summary>
private readonly CancellationToken _token;
/// <summary>Registration in <see cref="_token"/> that should be disposed of when the operation has completed.</summary>
private CancellationTokenRegistration _registration;
/// <summary>Initializes the cancelable reader.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the read operation.</param>
internal CancelableReaderInteractor(bool runContinuationsAsynchronously, CancellationToken cancellationToken) : base(runContinuationsAsynchronously)
{
_token = cancellationToken;
_registration = cancellationToken.Register(s =>
{
var thisRef = (CancelableReaderInteractor<T>)s;
thisRef.TrySetCanceled(thisRef._token);
}, this);
}
/// <summary>Unregister cancellation in case cancellation was registered.</summary>
internal override void UnregisterCancellation()
{
_registration.Dispose();
_registration = default;
}
}
/// <summary>A blocked or waiting reader where the read can be canceled.</summary>
/// <typeparam name="T">Specifies the type of data being read.</typeparam>
internal sealed class CancelableWriter<T> : WriterInteractor<T>
{
/// <summary>The token used for cancellation.</summary>
private CancellationToken _token;
/// <summary>Registration in <see cref="_token"/> that should be disposed of when the operation has completed.</summary>
private CancellationTokenRegistration _registration;
/// <summary>Initializes the cancelable writer.</summary>
/// <param name="runContinuationsAsynchronously">true if continuations should be forced to run asynchronously; otherwise, false.</param>
/// <param name="cancellationToken">A <see cref="CancellationToken"/> that can be used to cancel the read operation.</param>
internal CancelableWriter(bool runContinuationsAsynchronously, CancellationToken cancellationToken) : base(runContinuationsAsynchronously)
{
_token = cancellationToken;
_registration = cancellationToken.Register(s =>
{
var thisRef = (CancelableWriter<T>)s;
thisRef.TrySetCanceled(thisRef._token);
}, this);
}
/// <summary>Unregister cancellation in case cancellation was registered.</summary>
internal override void UnregisterCancellation()
{
_registration.Dispose();
_registration = default;
}
}
}

View File

@@ -13,7 +13,7 @@ namespace System.Threading.Channels
/// Provides a buffered channel of unbounded capacity for use by any number
/// of writers but at most a single reader at a time.
/// </summary>
[DebuggerDisplay("Items={ItemsCountForDebugger}")]
[DebuggerDisplay("Items={ItemsCountForDebugger}, Closed={ChannelIsClosedForDebugger}")]
[DebuggerTypeProxy(typeof(DebugEnumeratorDebugView<>))]
internal sealed class SingleConsumerUnboundedChannel<T> : Channel<T>, IDebugEnumerable<T>
{
@@ -31,8 +31,11 @@ namespace System.Threading.Channels
/// <summary>non-null if the channel has been marked as complete for writing.</summary>
private volatile Exception _doneWriting;
/// <summary>An <see cref="AsyncOperation{T}"/> if there's a blocked reader.</summary>
private AsyncOperation<T> _blockedReader;
/// <summary>A waiting reader (e.g. WaitForReadAsync) if there is one.</summary>
private ReaderInteractor<bool> _waitingReader;
private AsyncOperation<bool> _waitingReader;
/// <summary>Initialize the channel.</summary>
/// <param name="runContinuationsAsynchronously">Whether to force continuations to be executed asynchronously.</param>
@@ -45,13 +48,76 @@ namespace System.Threading.Channels
Writer = new UnboundedChannelWriter(this);
}
private sealed class UnboundedChannelReader : ChannelReader<T>
[DebuggerDisplay("Items={ItemsCountForDebugger}")]
[DebuggerTypeProxy(typeof(DebugEnumeratorDebugView<>))]
private sealed class UnboundedChannelReader : ChannelReader<T>, IDebugEnumerable<T>
{
internal readonly SingleConsumerUnboundedChannel<T> _parent;
internal UnboundedChannelReader(SingleConsumerUnboundedChannel<T> parent) => _parent = parent;
private readonly AsyncOperation<T> _readerSingleton;
private readonly AsyncOperation<bool> _waiterSingleton;
internal UnboundedChannelReader(SingleConsumerUnboundedChannel<T> parent)
{
_parent = parent;
_readerSingleton = new AsyncOperation<T>(parent._runContinuationsAsynchronously, pooled: true);
_waiterSingleton = new AsyncOperation<bool>(parent._runContinuationsAsynchronously, pooled: true);
}
public override Task Completion => _parent._completion.Task;
public override ValueTask<T> ReadAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask<T>(Task.FromCanceled<T>(cancellationToken));
}
if (TryRead(out T item))
{
return new ValueTask<T>(item);
}
SingleConsumerUnboundedChannel<T> parent = _parent;
AsyncOperation<T> oldBlockedReader, newBlockedReader;
lock (parent.SyncObj)
{
// Now that we hold the lock, try reading again.
if (TryRead(out item))
{
return new ValueTask<T>(item);
}
// If no more items will be written, fail the read.
if (parent._doneWriting != null)
{
return ChannelUtilities.GetInvalidCompletionValueTask<T>(parent._doneWriting);
}
// Try to use the singleton reader. If it's currently being used, then the channel
// is being used erroneously, and we cancel the outstanding operation.
oldBlockedReader = parent._blockedReader;
if (!cancellationToken.CanBeCanceled && _readerSingleton.TryOwnAndReset())
{
newBlockedReader = _readerSingleton;
if (newBlockedReader == oldBlockedReader)
{
// The previous operation completed, so null out the "old" reader
// so we don't end up canceling the new operation.
oldBlockedReader = null;
}
}
else
{
newBlockedReader = new AsyncOperation<T>(_parent._runContinuationsAsynchronously, cancellationToken);
}
parent._blockedReader = newBlockedReader;
}
oldBlockedReader?.TrySetCanceled();
return newBlockedReader.ValueTaskOfT;
}
public override bool TryRead(out T item)
{
SingleConsumerUnboundedChannel<T> parent = _parent;
@@ -66,55 +132,79 @@ namespace System.Threading.Channels
return false;
}
public override Task<bool> WaitToReadAsync(CancellationToken cancellationToken)
public override ValueTask<bool> WaitToReadAsync(CancellationToken cancellationToken)
{
// Outside of the lock, check if there are any items waiting to be read. If there are, we're done.
return
cancellationToken.IsCancellationRequested ? Task.FromCanceled<bool>(cancellationToken) :
!_parent._items.IsEmpty ? ChannelUtilities.s_trueTask :
WaitToReadAsyncCore(cancellationToken);
Task<bool> WaitToReadAsyncCore(CancellationToken ct)
if (cancellationToken.IsCancellationRequested)
{
SingleConsumerUnboundedChannel<T> parent = _parent;
ReaderInteractor<bool> oldWaiter = null, newWaiter;
lock (parent.SyncObj)
return new ValueTask<bool>(Task.FromCanceled<bool>(cancellationToken));
}
if (!_parent._items.IsEmpty)
{
return new ValueTask<bool>(true);
}
SingleConsumerUnboundedChannel<T> parent = _parent;
AsyncOperation<bool> oldWaitingReader = null, newWaitingReader;
lock (parent.SyncObj)
{
// Again while holding the lock, check to see if there are any items available.
if (!parent._items.IsEmpty)
{
// Again while holding the lock, check to see if there are any items available.
if (!parent._items.IsEmpty)
{
return ChannelUtilities.s_trueTask;
}
// There aren't any items; if we're done writing, there never will be more items.
if (parent._doneWriting != null)
{
return parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ?
Task.FromException<bool>(parent._doneWriting) :
ChannelUtilities.s_falseTask;
}
// Create the new waiter. We're a bit more tolerant of a stray waiting reader
// than we are of a blocked reader, as with usage patterns it's easier to leave one
// behind, so we just cancel any that may have been waiting around.
oldWaiter = parent._waitingReader;
parent._waitingReader = newWaiter = ReaderInteractor<bool>.Create(parent._runContinuationsAsynchronously, ct);
return new ValueTask<bool>(true);
}
oldWaiter?.TrySetCanceled();
return newWaiter.Task;
// There aren't any items; if we're done writing, there never will be more items.
if (parent._doneWriting != null)
{
return parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ?
new ValueTask<bool>(Task.FromException<bool>(parent._doneWriting)) :
default;
}
// Try to use the singleton waiter. If it's currently being used, then the channel
// is being used erroneously, and we cancel the outstanding operation.
oldWaitingReader = parent._waitingReader;
if (!cancellationToken.CanBeCanceled && _waiterSingleton.TryOwnAndReset())
{
newWaitingReader = _waiterSingleton;
if (newWaitingReader == oldWaitingReader)
{
// The previous operation completed, so null out the "old" waiter
// so we don't end up canceling the new operation.
oldWaitingReader = null;
}
}
else
{
newWaitingReader = new AsyncOperation<bool>(_parent._runContinuationsAsynchronously, cancellationToken);
}
parent._waitingReader = newWaitingReader;
}
oldWaitingReader?.TrySetCanceled();
return newWaitingReader.ValueTaskOfT;
}
/// <summary>Gets the number of items in the channel. This should only be used by the debugger.</summary>
private int ItemsCountForDebugger => _parent._items.Count;
/// <summary>Gets an enumerator the debugger can use to show the contents of the channel.</summary>
IEnumerator<T> IDebugEnumerable<T>.GetEnumerator() => _parent._items.GetEnumerator();
}
private sealed class UnboundedChannelWriter : ChannelWriter<T>
[DebuggerDisplay("Items={ItemsCountForDebugger}")]
[DebuggerTypeProxy(typeof(DebugEnumeratorDebugView<>))]
private sealed class UnboundedChannelWriter : ChannelWriter<T>, IDebugEnumerable<T>
{
internal readonly SingleConsumerUnboundedChannel<T> _parent;
internal UnboundedChannelWriter(SingleConsumerUnboundedChannel<T> parent) => _parent = parent;
public override bool TryComplete(Exception error)
{
ReaderInteractor<bool> waitingReader = null;
AsyncOperation<T> blockedReader = null;
AsyncOperation<bool> waitingReader = null;
bool completeTask = false;
SingleConsumerUnboundedChannel<T> parent = _parent;
@@ -136,6 +226,12 @@ namespace System.Threading.Channels
{
completeTask = true;
if (parent._blockedReader != null)
{
blockedReader = parent._blockedReader;
parent._blockedReader = null;
}
if (parent._waitingReader != null)
{
waitingReader = parent._waitingReader;
@@ -150,16 +246,26 @@ namespace System.Threading.Channels
ChannelUtilities.Complete(parent._completion, error);
}
// Complete a waiting reader if necessary.
Debug.Assert(blockedReader == null || waitingReader == null, "There should only ever be at most one reader.");
// Complete a blocked reader if necessary
if (blockedReader != null)
{
error = ChannelUtilities.CreateInvalidCompletionException(error);
blockedReader.TrySetException(error);
}
// Complete a waiting reader if necessary. (We really shouldn't have both a blockedReader
// and a waitingReader, but it's more expensive to prevent it than to just tolerate it.)
if (waitingReader != null)
{
if (error != null)
{
waitingReader.Fail(error);
waitingReader.TrySetException(error);
}
else
{
waitingReader.Success(item: false);
waitingReader.TrySetResult(item: false);
}
}
@@ -172,7 +278,8 @@ namespace System.Threading.Channels
SingleConsumerUnboundedChannel<T> parent = _parent;
while (true) // in case a reader was canceled and we need to try again
{
ReaderInteractor<bool> waitingReader = null;
AsyncOperation<T> blockedReader = null;
AsyncOperation<bool> waitingReader = null;
lock (parent.SyncObj)
{
@@ -182,42 +289,71 @@ namespace System.Threading.Channels
return false;
}
// Queue the item being written; then if there's a waiting
// reader, store it for notification outside of the lock.
parent._items.Enqueue(item);
waitingReader = parent._waitingReader;
if (waitingReader == null)
// If there's a blocked reader, store it into a local for completion outside of the lock.
// If there isn't a blocked reader, queue the item being written; then if there's a waiting
blockedReader = parent._blockedReader;
if (blockedReader != null)
{
return true;
parent._blockedReader = null;
}
else
{
parent._items.Enqueue(item);
waitingReader = parent._waitingReader;
if (waitingReader == null)
{
return true;
}
parent._waitingReader = null;
}
parent._waitingReader = null;
}
// If we get here, we grabbed a waiting reader.
// Notify it that an item was written and exit.
Debug.Assert(waitingReader != null, "Expected a waiting reader");
waitingReader.Success(item: true);
return true;
// If we get here, we grabbed a blocked or a waiting reader.
Debug.Assert((blockedReader != null) ^ (waitingReader != null), "Expected either a blocked or waiting reader, but not both");
// If we have a waiting reader, notify it that an item was written and exit.
if (waitingReader != null)
{
// If we get here, we grabbed a waiting reader.
waitingReader.TrySetResult(item: true);
return true;
}
// Otherwise we have a blocked reader: complete it with the item being written.
// In the case of a ReadAsync(CancellationToken), it's possible the reader could
// have been completed due to cancellation by the time we get here. In that case,
// we'll loop around to try again so as not to lose the item being written.
Debug.Assert(blockedReader != null);
if (blockedReader.TrySetResult(item))
{
return true;
}
}
}
public override Task<bool> WaitToWriteAsync(CancellationToken cancellationToken)
public override ValueTask<bool> WaitToWriteAsync(CancellationToken cancellationToken)
{
Exception doneWriting = _parent._doneWriting;
return
cancellationToken.IsCancellationRequested ? Task.FromCanceled<bool>(cancellationToken) :
doneWriting == null ? ChannelUtilities.s_trueTask :
doneWriting != ChannelUtilities.s_doneWritingSentinel ? Task.FromException<bool>(doneWriting) :
ChannelUtilities.s_falseTask;
cancellationToken.IsCancellationRequested ? new ValueTask<bool>(Task.FromCanceled<bool>(cancellationToken)) :
doneWriting == null ? new ValueTask<bool>(true) :
doneWriting != ChannelUtilities.s_doneWritingSentinel ? new ValueTask<bool>(Task.FromException<bool>(doneWriting)) :
default;
}
public override Task WriteAsync(T item, CancellationToken cancellationToken) =>
public override ValueTask WriteAsync(T item, CancellationToken cancellationToken) =>
// Writing always succeeds (unless we've already completed writing or cancellation has been requested),
// so just TryWrite and return a completed task.
cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
TryWrite(item) ? Task.CompletedTask :
Task.FromException(ChannelUtilities.CreateInvalidCompletionException(_parent._doneWriting));
cancellationToken.IsCancellationRequested ? new ValueTask(Task.FromCanceled(cancellationToken)) :
TryWrite(item) ? default :
new ValueTask(Task.FromException(ChannelUtilities.CreateInvalidCompletionException(_parent._doneWriting)));
/// <summary>Gets the number of items in the channel. This should only be used by the debugger.</summary>
private int ItemsCountForDebugger => _parent._items.Count;
/// <summary>Gets an enumerator the debugger can use to show the contents of the channel.</summary>
IEnumerator<T> IDebugEnumerable<T>.GetEnumerator() => _parent._items.GetEnumerator();
}
private object SyncObj => _items;
@@ -225,6 +361,9 @@ namespace System.Threading.Channels
/// <summary>Gets the number of items in the channel. This should only be used by the debugger.</summary>
private int ItemsCountForDebugger => _items.Count;
/// <summary>Report if the channel is closed or not. This should only be used by the debugger.</summary>
private bool ChannelIsClosedForDebugger => _doneWriting != null;
/// <summary>Gets an enumerator the debugger can use to show the contents of the channel.</summary>
IEnumerator<T> IDebugEnumerable<T>.GetEnumerator() => _items.GetEnumerator();
}

View File

@@ -10,7 +10,7 @@ using System.Threading.Tasks;
namespace System.Threading.Channels
{
/// <summary>Provides a buffered channel of unbounded capacity.</summary>
[DebuggerDisplay("Items={ItemsCountForDebugger}")]
[DebuggerDisplay("Items={ItemsCountForDebugger}, Closed={ChannelIsClosedForDebugger}")]
[DebuggerTypeProxy(typeof(DebugEnumeratorDebugView<>))]
internal sealed class UnboundedChannel<T> : Channel<T>, IDebugEnumerable<T>
{
@@ -18,11 +18,13 @@ namespace System.Threading.Channels
private readonly TaskCompletionSource<VoidResult> _completion;
/// <summary>The items in the channel.</summary>
private readonly ConcurrentQueue<T> _items = new ConcurrentQueue<T>();
/// <summary>Readers blocked reading from the channel.</summary>
private readonly Dequeue<AsyncOperation<T>> _blockedReaders = new Dequeue<AsyncOperation<T>>();
/// <summary>Whether to force continuations to be executed asynchronously from producer writes.</summary>
private readonly bool _runContinuationsAsynchronously;
/// <summary>Readers waiting for a notification that data is available.</summary>
private ReaderInteractor<bool> _waitingReaders;
private AsyncOperation<bool> _waitingReadersTail;
/// <summary>Set to non-null once Complete has been called.</summary>
private Exception _doneWriting;
@@ -31,17 +33,77 @@ namespace System.Threading.Channels
{
_runContinuationsAsynchronously = runContinuationsAsynchronously;
_completion = new TaskCompletionSource<VoidResult>(runContinuationsAsynchronously ? TaskCreationOptions.RunContinuationsAsynchronously : TaskCreationOptions.None);
base.Reader = new UnboundedChannelReader(this);
Reader = new UnboundedChannelReader(this);
Writer = new UnboundedChannelWriter(this);
}
private sealed class UnboundedChannelReader : ChannelReader<T>
[DebuggerDisplay("Items={ItemsCountForDebugger}")]
[DebuggerTypeProxy(typeof(DebugEnumeratorDebugView<>))]
private sealed class UnboundedChannelReader : ChannelReader<T>, IDebugEnumerable<T>
{
internal readonly UnboundedChannel<T> _parent;
internal UnboundedChannelReader(UnboundedChannel<T> parent) => _parent = parent;
private readonly AsyncOperation<T> _readerSingleton;
private readonly AsyncOperation<bool> _waiterSingleton;
internal UnboundedChannelReader(UnboundedChannel<T> parent)
{
_parent = parent;
_readerSingleton = new AsyncOperation<T>(parent._runContinuationsAsynchronously, pooled: true);
_waiterSingleton = new AsyncOperation<bool>(parent._runContinuationsAsynchronously, pooled: true);
}
public override Task Completion => _parent._completion.Task;
public override ValueTask<T> ReadAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask<T>(Task.FromCanceled<T>(cancellationToken));
}
// Dequeue an item if we can.
UnboundedChannel<T> parent = _parent;
if (parent._items.TryDequeue(out T item))
{
CompleteIfDone(parent);
return new ValueTask<T>(item);
}
lock (parent.SyncObj)
{
parent.AssertInvariants();
// Try to dequeue again, now that we hold the lock.
if (parent._items.TryDequeue(out item))
{
CompleteIfDone(parent);
return new ValueTask<T>(item);
}
// There are no items, so if we're done writing, fail.
if (parent._doneWriting != null)
{
return ChannelUtilities.GetInvalidCompletionValueTask<T>(parent._doneWriting);
}
// If we're able to use the singleton reader, do so.
if (!cancellationToken.CanBeCanceled)
{
AsyncOperation<T> singleton = _readerSingleton;
if (singleton.TryOwnAndReset())
{
parent._blockedReaders.EnqueueTail(singleton);
return singleton.ValueTaskOfT;
}
}
// Otherwise, create and queue a reader.
var reader = new AsyncOperation<T>(parent._runContinuationsAsynchronously, cancellationToken);
parent._blockedReaders.EnqueueTail(reader);
return reader.ValueTaskOfT;
}
}
public override bool TryRead(out T item)
{
UnboundedChannel<T> parent = _parent;
@@ -49,11 +111,7 @@ namespace System.Threading.Channels
// Dequeue an item if we can
if (parent._items.TryDequeue(out item))
{
if (parent._doneWriting != null && parent._items.IsEmpty)
{
// If we've now emptied the items queue and we're not getting any more, complete.
ChannelUtilities.Complete(parent._completion, parent._doneWriting);
}
CompleteIfDone(parent);
return true;
}
@@ -61,43 +119,75 @@ namespace System.Threading.Channels
return false;
}
public override Task<bool> WaitToReadAsync(CancellationToken cancellationToken)
private void CompleteIfDone(UnboundedChannel<T> parent)
{
// If there are any items, readers can try to get them.
return !_parent._items.IsEmpty ?
ChannelUtilities.s_trueTask :
WaitToReadAsyncCore(cancellationToken);
Task<bool> WaitToReadAsyncCore(CancellationToken ct)
if (parent._doneWriting != null && parent._items.IsEmpty)
{
UnboundedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
parent.AssertInvariants();
// Try again to read now that we're synchronized with writers.
if (!parent._items.IsEmpty)
{
return ChannelUtilities.s_trueTask;
}
// There are no items, so if we're done writing, there's never going to be data available.
if (parent._doneWriting != null)
{
return parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ?
Task.FromException<bool>(parent._doneWriting) :
ChannelUtilities.s_falseTask;
}
// Queue the waiter
return ChannelUtilities.GetOrCreateWaiter(ref parent._waitingReaders, parent._runContinuationsAsynchronously, ct);
}
// If we've now emptied the items queue and we're not getting any more, complete.
ChannelUtilities.Complete(parent._completion, parent._doneWriting);
}
}
public override ValueTask<bool> WaitToReadAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask<bool>(Task.FromCanceled<bool>(cancellationToken));
}
if (!_parent._items.IsEmpty)
{
return new ValueTask<bool>(true);
}
UnboundedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
parent.AssertInvariants();
// Try again to read now that we're synchronized with writers.
if (!parent._items.IsEmpty)
{
return new ValueTask<bool>(true);
}
// There are no items, so if we're done writing, there's never going to be data available.
if (parent._doneWriting != null)
{
return parent._doneWriting != ChannelUtilities.s_doneWritingSentinel ?
new ValueTask<bool>(Task.FromException<bool>(parent._doneWriting)) :
default;
}
// If we're able to use the singleton waiter, do so.
if (!cancellationToken.CanBeCanceled)
{
AsyncOperation<bool> singleton = _waiterSingleton;
if (singleton.TryOwnAndReset())
{
ChannelUtilities.QueueWaiter(ref parent._waitingReadersTail, singleton);
return singleton.ValueTaskOfT;
}
}
// Otherwise, create and queue a waiter.
var waiter = new AsyncOperation<bool>(parent._runContinuationsAsynchronously, cancellationToken);
ChannelUtilities.QueueWaiter(ref parent._waitingReadersTail, waiter);
return waiter.ValueTaskOfT;
}
}
/// <summary>Gets the number of items in the channel. This should only be used by the debugger.</summary>
private int ItemsCountForDebugger => _parent._items.Count;
/// <summary>Gets an enumerator the debugger can use to show the contents of the channel.</summary>
IEnumerator<T> IDebugEnumerable<T>.GetEnumerator() => _parent._items.GetEnumerator();
}
private sealed class UnboundedChannelWriter : ChannelWriter<T>
[DebuggerDisplay("Items={ItemsCountForDebugger}")]
[DebuggerTypeProxy(typeof(DebugEnumeratorDebugView<>))]
private sealed class UnboundedChannelWriter : ChannelWriter<T>, IDebugEnumerable<T>
{
internal readonly UnboundedChannel<T> _parent;
internal UnboundedChannelWriter(UnboundedChannel<T> parent) => _parent = parent;
@@ -132,12 +222,11 @@ namespace System.Threading.Channels
ChannelUtilities.Complete(parent._completion, error);
}
// At this point, _waitingReaders will not be mutated:
// it's only mutated by readers while holding the lock, and only if _doneWriting is null.
// We also know that only one thread (this one) will ever get here, as only that thread
// will be the one to transition from _doneWriting false to true. As such, we can
// freely manipulate _waitingReaders without any concurrency concerns.
ChannelUtilities.WakeUpWaiters(ref parent._waitingReaders, result: false, error: error);
// At this point, _blockedReaders and _waitingReaders will not be mutated:
// they're only mutated by readers while holding the lock, and only if _doneWriting is null.
// freely manipulate _blockedReaders and _waitingReaders without any concurrency concerns.
ChannelUtilities.FailOperations<AsyncOperation<T>, T>(parent._blockedReaders, ChannelUtilities.CreateInvalidCompletionException(error));
ChannelUtilities.WakeUpWaiters(ref parent._waitingReadersTail, result: false, error: error);
// Successfully transitioned to completed.
return true;
@@ -148,7 +237,8 @@ namespace System.Threading.Channels
UnboundedChannel<T> parent = _parent;
while (true)
{
ReaderInteractor<bool> waitingReaders = null;
AsyncOperation<T> blockedReader = null;
AsyncOperation<bool> waitingReadersTail = null;
lock (parent.SyncObj)
{
// If writing has already been marked as done, fail the write.
@@ -158,42 +248,70 @@ namespace System.Threading.Channels
return false;
}
// Add the data to the queue, and let any waiting readers know that they should try to read it.
// We can only complete such waiters here under the lock if they run continuations asynchronously
// (otherwise the synchronous continuations could be invoked under the lock). If we don't complete
// them here, we need to do so outside of the lock.
parent._items.Enqueue(item);
waitingReaders = parent._waitingReaders;
if (waitingReaders == null)
// If there aren't any blocked readers, just add the data to the queue,
// and let any waiting readers know that they should try to read it.
// We can only complete such waiters here under the lock if they run
// continuations asynchronously (otherwise the synchronous continuations
// could be invoked under the lock). If we don't complete them here, we
// need to do so outside of the lock.
if (parent._blockedReaders.IsEmpty)
{
parent._items.Enqueue(item);
waitingReadersTail = parent._waitingReadersTail;
if (waitingReadersTail == null)
{
return true;
}
parent._waitingReadersTail = null;
}
else
{
// There were blocked readers. Grab one, and then complete it outside of the lock.
blockedReader = parent._blockedReaders.DequeueHead();
}
}
if (blockedReader != null)
{
// Complete the reader. It's possible the reader was canceled, in which
// case we loop around to try everything again.
if (blockedReader.TrySetResult(item))
{
return true;
}
parent._waitingReaders = null;
}
// Wake up all of the waiters. Since we've released the lock, it's possible
// we could cause some spurious wake-ups here, if we tell a waiter there's
// something available but all data has already been removed. It's a benign
// race condition, though, as consumers already need to account for such things.
waitingReaders.Success(item: true);
return true;
else
{
// Wake up all of the waiters. Since we've released the lock, it's possible
// we could cause some spurious wake-ups here, if we tell a waiter there's
// something available but all data has already been removed. It's a benign
// race condition, though, as consumers already need to account for such things.
ChannelUtilities.WakeUpWaiters(ref waitingReadersTail, result: true);
return true;
}
}
}
public override Task<bool> WaitToWriteAsync(CancellationToken cancellationToken)
public override ValueTask<bool> WaitToWriteAsync(CancellationToken cancellationToken)
{
Exception doneWriting = _parent._doneWriting;
return
cancellationToken.IsCancellationRequested ? Task.FromCanceled<bool>(cancellationToken) :
doneWriting == null ? ChannelUtilities.s_trueTask : // unbounded writing can always be done if we haven't completed
doneWriting != ChannelUtilities.s_doneWritingSentinel ? Task.FromException<bool>(doneWriting) :
ChannelUtilities.s_falseTask;
cancellationToken.IsCancellationRequested ? new ValueTask<bool>(Task.FromCanceled<bool>(cancellationToken)) :
doneWriting == null ? new ValueTask<bool>(true) : // unbounded writing can always be done if we haven't completed
doneWriting != ChannelUtilities.s_doneWritingSentinel ? new ValueTask<bool>(Task.FromException<bool>(doneWriting)) :
default;
}
public override Task WriteAsync(T item, CancellationToken cancellationToken) =>
cancellationToken.IsCancellationRequested ? Task.FromCanceled(cancellationToken) :
TryWrite(item) ? ChannelUtilities.s_trueTask :
Task.FromException(ChannelUtilities.CreateInvalidCompletionException(_parent._doneWriting));
public override ValueTask WriteAsync(T item, CancellationToken cancellationToken) =>
cancellationToken.IsCancellationRequested ? new ValueTask(Task.FromCanceled(cancellationToken)) :
TryWrite(item) ? default :
new ValueTask(Task.FromException(ChannelUtilities.CreateInvalidCompletionException(_parent._doneWriting)));
/// <summary>Gets the number of items in the channel. This should only be used by the debugger.</summary>
private int ItemsCountForDebugger => _parent._items.Count;
/// <summary>Gets an enumerator the debugger can use to show the contents of the channel.</summary>
IEnumerator<T> IDebugEnumerable<T>.GetEnumerator() => _parent._items.GetEnumerator();
}
/// <summary>Gets the object used to synchronize access to all state on this instance.</summary>
@@ -209,11 +327,12 @@ namespace System.Threading.Channels
{
if (_runContinuationsAsynchronously)
{
Debug.Assert(_waitingReaders == null, "There's data available, so there shouldn't be any waiting readers.");
Debug.Assert(_blockedReaders.IsEmpty, "There's data available, so there shouldn't be any blocked readers.");
Debug.Assert(_waitingReadersTail == null, "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 (_waitingReaders != null && _runContinuationsAsynchronously)
if ((!_blockedReaders.IsEmpty || _waitingReadersTail != null) && _runContinuationsAsynchronously)
{
Debug.Assert(_items.IsEmpty, "There are blocked/waiting readers, so there shouldn't be any data available.");
}
@@ -221,11 +340,14 @@ namespace System.Threading.Channels
{
Debug.Assert(_doneWriting != null, "We're completed, so we must be done writing.");
}
}
}
/// <summary>Gets the number of items in the channel. This should only be used by the debugger.</summary>
private int ItemsCountForDebugger => _items.Count;
/// <summary>Report if the channel is closed or not. This should only be used by the debugger.</summary>
private bool ChannelIsClosedForDebugger => _doneWriting != null;
/// <summary>Gets an enumerator the debugger can use to show the contents of the channel.</summary>
IEnumerator<T> IDebugEnumerable<T>.GetEnumerator() => _items.GetEnumerator();
}

View File

@@ -1,324 +0,0 @@
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Collections.Generic;
using System.Diagnostics;
using System.Threading.Tasks;
namespace System.Threading.Channels
{
/// <summary>Provides an unbuffered channel, such that a reader and a writer must rendezvous to succeed.</summary>
[DebuggerDisplay("Writers Waiting/Blocked: {WaitingWritersForDebugger}/{BlockedWritersCountForDebugger}, Readers Waiting/Blocked: {WaitingReadersForDebugger}/{BlockedReadersCountForDebugger}")]
[DebuggerTypeProxy(typeof(UnbufferedChannel<>.DebugView))]
internal sealed class UnbufferedChannel<T> : Channel<T>
{
/// <summary>Task that represents the completion of the channel.</summary>
private readonly TaskCompletionSource<VoidResult> _completion = new TaskCompletionSource<VoidResult>(TaskCreationOptions.RunContinuationsAsynchronously);
/// <summary>A queue of readers blocked waiting to be matched with a writer.</summary>
private readonly Dequeue<ReaderInteractor<T>> _blockedReaders = new Dequeue<ReaderInteractor<T>>();
/// <summary>A queue of writers blocked waiting to be matched with a reader.</summary>
private readonly Dequeue<WriterInteractor<T>> _blockedWriters = new Dequeue<WriterInteractor<T>>();
/// <summary>Task signaled when any WaitToReadAsync waiters should be woken up.</summary>
private ReaderInteractor<bool> _waitingReaders;
/// <summary>Task signaled when any WaitToReadAsync waiters should be woken up.</summary>
private ReaderInteractor<bool> _waitingWriters;
private sealed class UnbufferedChannelReader : ChannelReader<T>
{
internal readonly UnbufferedChannel<T> _parent;
internal UnbufferedChannelReader(UnbufferedChannel<T> parent) => _parent = parent;
public override Task Completion => _parent._completion.Task;
public override bool TryRead(out T item)
{
UnbufferedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
parent.AssertInvariants();
// Try to find a writer to pair with
while (!parent._blockedWriters.IsEmpty)
{
WriterInteractor<T> w = parent._blockedWriters.DequeueHead();
if (w.Success(default))
{
item = w.Item;
return true;
}
}
}
// None found
item = default;
return false;
}
public override ValueTask<T> ReadAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return new ValueTask<T>(Task.FromCanceled<T>(cancellationToken));
}
UnbufferedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
parent.AssertInvariants();
// If we're already completed, nothing to read.
if (parent._completion.Task.IsCompleted)
{
return new ValueTask<T>(
parent._completion.Task.IsCanceled ? Task.FromCanceled<T>(new CancellationToken(true)) :
Task.FromException<T>(
parent._completion.Task.IsFaulted ?
ChannelUtilities.CreateInvalidCompletionException(parent._completion.Task.Exception.InnerException) :
ChannelUtilities.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 (!parent._blockedWriters.IsEmpty)
{
WriterInteractor<T> w = parent._blockedWriters.DequeueHead();
if (w.Success(default(VoidResult)))
{
return new ValueTask<T>(w.Item);
}
}
// No writer found to pair with. Queue the reader.
var r = ReaderInteractor<T>.Create(true, cancellationToken);
parent._blockedReaders.EnqueueTail(r);
// And let any waiting writers know it's their lucky day.
ChannelUtilities.WakeUpWaiters(ref parent._waitingWriters, result: true);
return new ValueTask<T>(r.Task);
}
}
public override Task<bool> WaitToReadAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<bool>(cancellationToken);
}
UnbufferedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
// If we're done writing, fail.
if (parent._completion.Task.IsCompleted)
{
return parent._completion.Task.IsFaulted ?
Task.FromException<bool>(parent._completion.Task.Exception.InnerException) :
ChannelUtilities.s_falseTask;
}
// If there's a blocked writer, we can read.
if (!parent._blockedWriters.IsEmpty)
{
return ChannelUtilities.s_trueTask;
}
// Otherwise, queue the waiter.
return ChannelUtilities.GetOrCreateWaiter(ref parent._waitingReaders, runContinuationsAsynchronously: true, cancellationToken);
}
}
}
private sealed class UnbufferedChannelWriter : ChannelWriter<T>
{
internal readonly UnbufferedChannel<T> _parent;
internal UnbufferedChannelWriter(UnbufferedChannel<T> parent) => _parent = parent;
public override bool TryComplete(Exception error)
{
UnbufferedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
parent.AssertInvariants();
// Mark the channel as being done. Since there's no buffered data, we can complete immediately.
if (parent._completion.Task.IsCompleted)
{
return false;
}
ChannelUtilities.Complete(parent._completion, error);
// Fail any blocked readers/writers, as there will be no writers/readers to pair them with.
ChannelUtilities.FailInteractors<ReaderInteractor<T>, T>(parent._blockedReaders, ChannelUtilities.CreateInvalidCompletionException(error));
ChannelUtilities.FailInteractors<WriterInteractor<T>, VoidResult>(parent._blockedWriters, ChannelUtilities.CreateInvalidCompletionException(error));
// Let any waiting readers and writers know there won't be any more data
ChannelUtilities.WakeUpWaiters(ref parent._waitingReaders, result: false, error: error);
ChannelUtilities.WakeUpWaiters(ref parent._waitingWriters, result: false, error: error);
}
return true;
}
public override bool TryWrite(T item)
{
UnbufferedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
parent.AssertInvariants();
// Try to find a reader to pair with
while (!parent._blockedReaders.IsEmpty)
{
ReaderInteractor<T> r = parent._blockedReaders.DequeueHead();
if (r.Success(item))
{
return true;
}
}
}
// None found
return false;
}
public override Task<bool> WaitToWriteAsync(CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled<bool>(cancellationToken);
}
UnbufferedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
// If we're done writing, fail.
if (parent._completion.Task.IsCompleted)
{
return parent._completion.Task.IsFaulted ?
Task.FromException<bool>(parent._completion.Task.Exception.InnerException) :
ChannelUtilities.s_falseTask;
}
// If there's a blocked reader, we can write
if (!parent._blockedReaders.IsEmpty)
{
return ChannelUtilities.s_trueTask;
}
// Otherwise, queue the writer
return ChannelUtilities.GetOrCreateWaiter(ref parent._waitingWriters, true, cancellationToken);
}
}
public override Task WriteAsync(T item, CancellationToken cancellationToken)
{
if (cancellationToken.IsCancellationRequested)
{
return Task.FromCanceled(cancellationToken);
}
UnbufferedChannel<T> parent = _parent;
lock (parent.SyncObj)
{
// Fail if we've already completed
if (parent._completion.Task.IsCompleted)
{
return
parent._completion.Task.IsCanceled ? Task.FromCanceled<T>(new CancellationToken(true)) :
Task.FromException<T>(
parent._completion.Task.IsFaulted ?
ChannelUtilities.CreateInvalidCompletionException(parent._completion.Task.Exception.InnerException) :
ChannelUtilities.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 (!parent._blockedReaders.IsEmpty)
{
ReaderInteractor<T> r = parent._blockedReaders.DequeueHead();
if (r.Success(item))
{
return Task.CompletedTask;
}
}
// No reader was available. Queue the writer.
var w = WriterInteractor<T>.Create(true, item, cancellationToken);
parent._blockedWriters.EnqueueTail(w);
// And let any waiting readers know it's their lucky day.
ChannelUtilities.WakeUpWaiters(ref parent._waitingReaders, result: true);
return w.Task;
}
}
}
/// <summary>Initialize the channel.</summary>
internal UnbufferedChannel()
{
base.Reader = new UnbufferedChannelReader(this);
Writer = new UnbufferedChannelWriter(this);
}
/// <summary>Gets an object used to synchronize all state on the instance.</summary>
private object SyncObj => _completion;
[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.IsEmpty)
{
Debug.Assert(_blockedWriters.IsEmpty, "If there are blocked readers, there can't be blocked writers.");
}
if (!_blockedWriters.IsEmpty)
{
Debug.Assert(_blockedReaders.IsEmpty, "If there are blocked writers, there can't be blocked readers.");
}
if (_completion.Task.IsCompleted)
{
Debug.Assert(_blockedReaders.IsEmpty, "No readers can be blocked after we've completed.");
Debug.Assert(_blockedWriters.IsEmpty, "No writers can be blocked after we've completed.");
}
}
/// <summary>Gets whether there are any waiting writers. This should only be used by the debugger.</summary>
private bool WaitingWritersForDebugger => _waitingWriters != null;
/// <summary>Gets whether there are any waiting readers. This should only be used by the debugger.</summary>
private bool WaitingReadersForDebugger => _waitingReaders != null;
/// <summary>Gets the number of blocked writers. This should only be used by the debugger.</summary>
private int BlockedWritersCountForDebugger => _blockedWriters.Count;
/// <summary>Gets the number of blocked readers. This should only be used by the debugger.</summary>
private int BlockedReadersCountForDebugger => _blockedReaders.Count;
private sealed class DebugView
{
private readonly UnbufferedChannel<T> _channel;
public DebugView(UnbufferedChannel<T> channel) => _channel = channel;
public bool WaitingReaders => _channel._waitingReaders != null;
public bool WaitingWriters => _channel._waitingWriters != null;
public int BlockedReaders => _channel._blockedReaders.Count;
public T[] BlockedWriters
{
get
{
var items = new List<T>();
foreach (WriterInteractor<T> blockedWriter in _channel._blockedWriters)
{
items.Add(blockedWriter.Item);
}
return items.ToArray();
}
}
}
}
}

View File

@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using System.Threading.Tasks;
using Xunit;
@@ -8,11 +9,11 @@ namespace System.Threading.Channels.Tests
{
public class BoundedChannelTests : ChannelTestBase
{
protected override Channel<int> CreateChannel() => Channel.CreateBounded<int>(1);
protected override Channel<int> CreateFullChannel()
protected override Channel<T> CreateChannel<T>() => Channel.CreateBounded<T>(new BoundedChannelOptions(1) { AllowSynchronousContinuations = AllowSynchronousContinuations });
protected override Channel<T> CreateFullChannel<T>()
{
var c = Channel.CreateBounded<int>(1);
c.Writer.WriteAsync(42).Wait();
var c = Channel.CreateBounded<T>(new BoundedChannelOptions(1) { AllowSynchronousContinuations = AllowSynchronousContinuations });
c.Writer.WriteAsync(default).AsTask().Wait();
return c;
}
@@ -217,16 +218,16 @@ namespace System.Threading.Channels.Tests
public async Task CancelPendingWrite_Reading_DataTransferredFromCorrectWriter()
{
var c = Channel.CreateBounded<int>(1);
Assert.Equal(TaskStatus.RanToCompletion, c.Writer.WriteAsync(42).Status);
Assert.True(c.Writer.WriteAsync(42).IsCompletedSuccessfully);
var cts = new CancellationTokenSource();
Task write1 = c.Writer.WriteAsync(43, cts.Token);
Task write1 = c.Writer.WriteAsync(43, cts.Token).AsTask();
Assert.Equal(TaskStatus.WaitingForActivation, write1.Status);
cts.Cancel();
Task write2 = c.Writer.WriteAsync(44);
Task write2 = c.Writer.WriteAsync(44).AsTask();
Assert.Equal(42, await c.Reader.ReadAsync());
Assert.Equal(44, await c.Reader.ReadAsync());
@@ -341,10 +342,10 @@ namespace System.Threading.Channels.Tests
var c = Channel.CreateBounded<int>(1);
Assert.True(c.Writer.TryWrite(1));
Task<bool> write1 = c.Writer.WaitToWriteAsync();
Task<bool> write1 = c.Writer.WaitToWriteAsync().AsTask();
Assert.False(write1.IsCompleted);
Task<bool> write2 = c.Writer.WaitToWriteAsync();
Task<bool> write2 = c.Writer.WaitToWriteAsync().AsTask();
Assert.False(write2.IsCompleted);
Assert.Equal(1, await c.Reader.ReadAsync());
@@ -361,12 +362,12 @@ namespace System.Threading.Channels.Tests
var c = Channel.CreateBounded<int>(new BoundedChannelOptions(1) { AllowSynchronousContinuations = allowSynchronousContinuations });
int expectedId = Environment.CurrentManagedThreadId;
Task r = c.Reader.WaitToReadAsync().ContinueWith(_ =>
Task r = c.Reader.WaitToReadAsync().AsTask().ContinueWith(_ =>
{
Assert.Equal(allowSynchronousContinuations, expectedId == Environment.CurrentManagedThreadId);
}, CancellationToken.None, TaskContinuationOptions.ExecuteSynchronously, TaskScheduler.Default);
Assert.Equal(TaskStatus.RanToCompletion, c.Writer.WriteAsync(42).Status);
Assert.True(c.Writer.WriteAsync(42).IsCompletedSuccessfully);
((IAsyncResult)r).AsyncWaitHandle.WaitOne(); // avoid inlining the continuation
r.GetAwaiter().GetResult();
}
@@ -390,13 +391,13 @@ namespace System.Threading.Channels.Tests
}
[Fact]
public void TryWrite_NoBlockedReaders_WaitingReader_WaiterNotifified()
public async Task TryWrite_NoBlockedReaders_WaitingReader_WaiterNotified()
{
Channel<int> c = CreateChannel();
Task<bool> r = c.Reader.WaitToReadAsync();
Task<bool> r = c.Reader.WaitToReadAsync().AsTask();
Assert.True(c.Writer.TryWrite(42));
AssertSynchronousTrue(r);
Assert.True(await r);
}
}
}

View File

@@ -1,5 +1,6 @@
// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for full license information.
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
using Xunit;

File diff suppressed because it is too large Load Diff

Some files were not shown because too many files have changed in this diff Show More