You've already forked linux-packaging-mono
Imported Upstream version 5.16.0.100
Former-commit-id: 38faa55fb9669e35e7d8448b15c25dc447f25767
This commit is contained in:
parent
0a9828183b
commit
7d7f676260
@@ -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
|
||||
|
||||
@@ -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() { }
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
@@ -4,6 +4,7 @@
|
||||
<BuildConfigurations>
|
||||
netstandard1.3;
|
||||
netstandard;
|
||||
netcoreapp;
|
||||
</BuildConfigurations>
|
||||
</PropertyGroup>
|
||||
</Project>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
449
external/corefx/src/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.cs
vendored
Normal file
449
external/corefx/src/System.Threading.Channels/src/System/Threading/Channels/AsyncOperation.cs
vendored
Normal 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; }
|
||||
}
|
||||
}
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
Reference in New Issue
Block a user