зеркало из https://github.com/dotnet/razor.git
Copy AsyncBatchingWorkQueue from Roslyn
Roslyn's `AsyncBatchingWorkQueue` is a battle-hardened utility that provides a robust mechanism to do work in batches without blocking.
This commit is contained in:
Родитель
d4a6aaafa0
Коммит
530fd6d13d
|
@ -151,6 +151,7 @@
|
|||
<NuGetSolutionRestoreManagerInteropVersion>4.8.0</NuGetSolutionRestoreManagerInteropVersion>
|
||||
<StreamJsonRpcPackageVersion>2.17.11</StreamJsonRpcPackageVersion>
|
||||
<SystemRuntimeInteropServicesRuntimePackageVersion>4.3.0</SystemRuntimeInteropServicesRuntimePackageVersion>
|
||||
<SystemThreadingTasksExtensions>4.5.4</SystemThreadingTasksExtensions>
|
||||
<Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion>3.11.0-beta1.24170.2</Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion>
|
||||
<Tooling_MicrosoftCodeAnalysisBannedApiAnalyzersPackageVersion>$(Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion)</Tooling_MicrosoftCodeAnalysisBannedApiAnalyzersPackageVersion>
|
||||
<Tooling_RoslynDiagnosticsAnalyzersPackageVersion>$(Tooling_MicrosoftCodeAnalysisAnalyzersPackageVersion)</Tooling_RoslynDiagnosticsAnalyzersPackageVersion>
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using Microsoft.AspNetCore.Razor.Threading;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace;
|
||||
|
||||
// Copied from https://github.com/dotnet/project-system/blob/e4db47666e0a49f6c38e701f8630dbc31380fb64/src/Microsoft.VisualStudio.ProjectSystem.Managed/Threading/Tasks/TaskDelayScheduler.cs
|
||||
|
|
|
@ -14,6 +14,7 @@
|
|||
<ItemGroup>
|
||||
<PackageReference Include="Microsoft.CodeAnalysis.Workspaces.Common" Version="$(MicrosoftCodeAnalysisWorkspacesCommonPackageVersion)" />
|
||||
<PackageReference Include="MessagePack" Version="$(MessagePackPackageVersion)" />
|
||||
<PackageReference Include="System.Threading.Tasks.Extensions" Version="$(SystemThreadingTasksExtensions)" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
|
|
@ -1,13 +1,31 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.ExternalAccess.RoslynWorkspace;
|
||||
using System;
|
||||
using System.Threading;
|
||||
|
||||
// Copied from https://github.com/dotnet/project-system/blob/e4db47666e0a49f6c38e701f8630dbc31380fb64/src/Microsoft.VisualStudio.ProjectSystem.Managed/Threading/Tasks/CancellationSeries.cs
|
||||
namespace Microsoft.AspNetCore.Razor.Threading;
|
||||
|
||||
// NOTE: This code is copied from dotnet/roslyn:
|
||||
// https://github.com/dotnet/roslyn/blob/98cd097bf122677378692ebe952b71ab6e5bb013/src/Workspaces/Core/Portable/Utilities/CancellationSeries.cs
|
||||
//
|
||||
// However, it was originally derived from an implementation in dotnet/project-system:
|
||||
// https://github.com/dotnet/project-system/blob/bdf69d5420ec8d894f5bf4c3d4692900b7f2479c/src/Microsoft.VisualStudio.ProjectSystem.Managed/Threading/Tasks/CancellationSeries.cs
|
||||
//
|
||||
// See the commentary in https://github.com/dotnet/roslyn/pull/50156 for notes on incorporating changes made to the
|
||||
// reference implementation.
|
||||
|
||||
/// <summary>
|
||||
/// Produces a series of <see cref="CancellationToken"/> objects such that requesting a new token
|
||||
/// causes the previously issued token to be cancelled.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// <para>Consuming code is responsible for managing overlapping asynchronous operations.</para>
|
||||
/// <para>This class has a lock-free implementation to minimise latency and contention.</para>
|
||||
/// </remarks>
|
||||
internal sealed class CancellationSeries : IDisposable
|
||||
{
|
||||
private CancellationTokenSource? _cts = new();
|
||||
private CancellationTokenSource? _cts;
|
||||
|
||||
private readonly CancellationToken _superToken;
|
||||
|
||||
|
@ -16,11 +34,21 @@ internal sealed class CancellationSeries : IDisposable
|
|||
/// </summary>
|
||||
/// <param name="token">An optional cancellation token that, when cancelled, cancels the last
|
||||
/// issued token and causes any subsequent tokens to be issued in a cancelled state.</param>
|
||||
public CancellationSeries(CancellationToken token)
|
||||
public CancellationSeries(CancellationToken token = default)
|
||||
{
|
||||
// Initialize with a pre-cancelled source to ensure HasActiveToken has the correct state
|
||||
_cts = new CancellationTokenSource();
|
||||
_cts.Cancel();
|
||||
|
||||
_superToken = token;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Determines if the cancellation series has an active token which has not been cancelled.
|
||||
/// </summary>
|
||||
public bool HasActiveToken
|
||||
=> _cts is { IsCancellationRequested: false };
|
||||
|
||||
/// <summary>
|
||||
/// Creates the next <see cref="CancellationToken"/> in the series, ensuring the last issued
|
||||
/// token (if any) is cancelled first.
|
||||
|
@ -37,7 +65,7 @@ internal sealed class CancellationSeries : IDisposable
|
|||
/// </list>
|
||||
/// </returns>
|
||||
/// <exception cref="ObjectDisposedException">This object has been disposed.</exception>
|
||||
public CancellationToken CreateNext(CancellationToken token)
|
||||
public CancellationToken CreateNext(CancellationToken token = default)
|
||||
{
|
||||
var nextSource = CancellationTokenSource.CreateLinkedTokenSource(token, _superToken);
|
||||
|
||||
|
@ -46,9 +74,25 @@ internal sealed class CancellationSeries : IDisposable
|
|||
// This way we would return a cancelled token, which is reasonable.
|
||||
var nextToken = nextSource.Token;
|
||||
|
||||
var priorSource = Interlocked.Exchange(ref _cts, nextSource);
|
||||
// The following block is identical to Interlocked.Exchange, except no replacement is made if the current
|
||||
// field value is null (latch on null). This ensures state is not corrupted if CreateNext is called after
|
||||
// the object is disposed.
|
||||
var priorSource = Volatile.Read(ref _cts);
|
||||
while (priorSource is not null)
|
||||
{
|
||||
var candidate = Interlocked.CompareExchange(ref _cts, nextSource, priorSource);
|
||||
|
||||
if (priorSource is null)
|
||||
if (candidate == priorSource)
|
||||
{
|
||||
break;
|
||||
}
|
||||
else
|
||||
{
|
||||
priorSource = candidate;
|
||||
}
|
||||
}
|
||||
|
||||
if (priorSource == null)
|
||||
{
|
||||
nextSource.Dispose();
|
||||
|
||||
|
@ -73,7 +117,7 @@ internal sealed class CancellationSeries : IDisposable
|
|||
{
|
||||
var source = Interlocked.Exchange(ref _cts, null);
|
||||
|
||||
if (source is null)
|
||||
if (source == null)
|
||||
{
|
||||
// Already disposed
|
||||
return;
|
|
@ -0,0 +1,73 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor.Threading;
|
||||
|
||||
internal static class TaskExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// Asserts the <see cref="Task"/> passed has already been completed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is useful for a specific case: sometimes you might be calling an API that is "sometimes" async, and you're
|
||||
/// calling it from a synchronous method where you know it should have completed synchronously. This is an easy
|
||||
/// way to assert that while silencing any compiler complaints.
|
||||
/// </remarks>
|
||||
public static void VerifyCompleted(this Task task)
|
||||
{
|
||||
Assumed.True(task.IsCompleted);
|
||||
|
||||
// Propagate any exceptions that may have been thrown.
|
||||
task.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts the <see cref="Task"/> passed has already been completed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is useful for a specific case: sometimes you might be calling an API that is "sometimes" async, and you're
|
||||
/// calling it from a synchronous method where you know it should have completed synchronously. This is an easy
|
||||
/// way to assert that while silencing any compiler complaints.
|
||||
/// </remarks>
|
||||
public static TResult VerifyCompleted<TResult>(this Task<TResult> task)
|
||||
{
|
||||
Assumed.True(task.IsCompleted);
|
||||
|
||||
// Propagate any exceptions that may have been thrown.
|
||||
return task.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts the <see cref="ValueTask"/> passed has already been completed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is useful for a specific case: sometimes you might be calling an API that is "sometimes" async, and you're
|
||||
/// calling it from a synchronous method where you know it should have completed synchronously. This is an easy
|
||||
/// way to assert that while silencing any compiler complaints.
|
||||
/// </remarks>
|
||||
public static void VerifyCompleted(this ValueTask task)
|
||||
{
|
||||
Assumed.True(task.IsCompleted);
|
||||
|
||||
// Propagate any exceptions that may have been thrown.
|
||||
task.GetAwaiter().GetResult();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Asserts the <see cref="ValueTask"/> passed has already been completed.
|
||||
/// </summary>
|
||||
/// <remarks>
|
||||
/// This is useful for a specific case: sometimes you might be calling an API that is "sometimes" async, and you're
|
||||
/// calling it from a synchronous method where you know it should have completed synchronously. This is an easy
|
||||
/// way to assert that while silencing any compiler complaints.
|
||||
/// </remarks>
|
||||
public static TResult VerifyCompleted<TResult>(this ValueTask<TResult> task)
|
||||
{
|
||||
Assumed.True(task.IsCompleted);
|
||||
|
||||
// Propagate any exceptions that may have been thrown.
|
||||
return task.GetAwaiter().GetResult();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,27 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Utilities;
|
||||
|
||||
// NOTE: This code is copied and modified slightly from dotnet/roslyn:
|
||||
// https://github.com/dotnet/roslyn/blob/98cd097bf122677378692ebe952b71ab6e5bb013/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue%600.cs
|
||||
|
||||
/// <inheritdoc cref="AsyncBatchingWorkQueue{TItem, TResult}"/>
|
||||
internal class AsyncBatchingWorkQueue(
|
||||
TimeSpan delay,
|
||||
Func<CancellationToken, ValueTask> processBatchAsync,
|
||||
CancellationToken cancellationToken) : AsyncBatchingWorkQueue<VoidResult>(delay, Convert(processBatchAsync), EqualityComparer<VoidResult>.Default, cancellationToken)
|
||||
{
|
||||
private static Func<ImmutableArray<VoidResult>, CancellationToken, ValueTask> Convert(Func<CancellationToken, ValueTask> processBatchAsync)
|
||||
=> (items, ct) => processBatchAsync(ct);
|
||||
|
||||
public void AddWork(bool cancelExistingWork = false)
|
||||
=> base.AddWork(default(VoidResult), cancelExistingWork);
|
||||
}
|
|
@ -0,0 +1,43 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Utilities;
|
||||
|
||||
// NOTE: This code is copied and modified slightly from dotnet/roslyn:
|
||||
// https://github.com/dotnet/roslyn/blob/98cd097bf122677378692ebe952b71ab6e5bb013/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue%601.cs
|
||||
|
||||
/// <inheritdoc cref="AsyncBatchingWorkQueue{TItem, TResult}"/>
|
||||
internal class AsyncBatchingWorkQueue<TItem>(
|
||||
TimeSpan delay,
|
||||
Func<ImmutableArray<TItem>, CancellationToken, ValueTask> processBatchAsync,
|
||||
IEqualityComparer<TItem>? equalityComparer,
|
||||
CancellationToken cancellationToken) : AsyncBatchingWorkQueue<TItem, VoidResult>(delay, Convert(processBatchAsync), equalityComparer, cancellationToken)
|
||||
{
|
||||
public AsyncBatchingWorkQueue(
|
||||
TimeSpan delay,
|
||||
Func<ImmutableArray<TItem>, CancellationToken, ValueTask> processBatchAsync,
|
||||
CancellationToken cancellationToken)
|
||||
: this(delay,
|
||||
processBatchAsync,
|
||||
equalityComparer: null,
|
||||
cancellationToken)
|
||||
{
|
||||
}
|
||||
|
||||
private static Func<ImmutableArray<TItem>, CancellationToken, ValueTask<VoidResult>> Convert(Func<ImmutableArray<TItem>, CancellationToken, ValueTask> processBatchAsync)
|
||||
=> async (items, ct) =>
|
||||
{
|
||||
await processBatchAsync(items, ct).ConfigureAwait(false);
|
||||
return default;
|
||||
};
|
||||
|
||||
public new Task WaitUntilCurrentBatchCompletesAsync()
|
||||
=> base.WaitUntilCurrentBatchCompletesAsync();
|
||||
}
|
|
@ -0,0 +1,292 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.Immutable;
|
||||
using System.Threading;
|
||||
using System.Threading.Tasks;
|
||||
using Microsoft.AspNetCore.Razor;
|
||||
using Microsoft.AspNetCore.Razor.PooledObjects;
|
||||
using Microsoft.AspNetCore.Razor.Threading;
|
||||
using Microsoft.VisualStudio.Threading;
|
||||
|
||||
namespace Microsoft.CodeAnalysis.Razor.Utilities;
|
||||
|
||||
// NOTE: This code is derived from dotnet/roslyn:
|
||||
// https://github.com/dotnet/roslyn/blob/98cd097bf122677378692ebe952b71ab6e5bb013/src/Workspaces/Core/Portable/Shared/Utilities/AsyncBatchingWorkQueue%602.cs
|
||||
|
||||
/// <summary>
|
||||
/// A queue where items can be added to to be processed in batches after some delay has passed. When processing
|
||||
/// happens, all the items added since the last processing point will be passed along to be worked on. Rounds of
|
||||
/// processing happen serially, only starting up after a previous round has completed.
|
||||
/// <para>
|
||||
/// Failure to complete a particular batch (either due to cancellation or some faulting error) will not prevent
|
||||
/// further batches from executing. The only thing that will permanently stop this queue from processing items is if
|
||||
/// the <see cref="CancellationToken"/> passed to the constructor switches to <see
|
||||
/// cref="CancellationToken.IsCancellationRequested"/>.
|
||||
/// </para>
|
||||
/// </summary>
|
||||
internal class AsyncBatchingWorkQueue<TItem, TResult>
|
||||
{
|
||||
/// <summary>
|
||||
/// Delay we wait after finishing the processing of one batch and starting up on then.
|
||||
/// </summary>
|
||||
private readonly TimeSpan _delay;
|
||||
|
||||
/// <summary>
|
||||
/// Equality comparer uses to dedupe items if present.
|
||||
/// </summary>
|
||||
private readonly IEqualityComparer<TItem>? _equalityComparer;
|
||||
|
||||
/// <summary>
|
||||
/// Callback to actually perform the processing of the next batch of work.
|
||||
/// </summary>
|
||||
private readonly Func<ImmutableArray<TItem>, CancellationToken, ValueTask<TResult>> _processBatchAsync;
|
||||
|
||||
/// <summary>
|
||||
/// Cancellation token controlling the entire queue. Once this is triggered, we don't want to do any more work
|
||||
/// at all.
|
||||
/// </summary>
|
||||
private readonly CancellationToken _entireQueueCancellationToken;
|
||||
|
||||
/// <summary>
|
||||
/// Cancellation series we use so we can cancel individual batches of work if requested. The client of the
|
||||
/// queue can cancel existing work by either calling <see cref="CancelExistingWork"/> directly, or passing <see
|
||||
/// langword="true"/> to <see cref="AddWork(TItem, bool)"/>. Work in the queue that has not started will be
|
||||
/// immediately discarded. The cancellation token passed to <see cref="_processBatchAsync"/> will be triggered
|
||||
/// allowing the client callback to cooperatively cancel the current batch of work it is performing.
|
||||
/// </summary>
|
||||
private readonly CancellationSeries _cancellationSeries;
|
||||
|
||||
#region protected by lock
|
||||
|
||||
/// <summary>
|
||||
/// Lock we will use to ensure the remainder of these fields can be accessed in a thread-safe
|
||||
/// manner. When work is added we'll place the data into <see cref="_nextBatch"/>.
|
||||
/// We'll then kick of a task to process this in the future if we don't already have an
|
||||
/// existing task in flight for that.
|
||||
/// </summary>
|
||||
private readonly object _gate = new();
|
||||
|
||||
/// <summary>
|
||||
/// Data added that we want to process in our next update task.
|
||||
/// </summary>
|
||||
private readonly ImmutableArray<TItem>.Builder _nextBatch = ImmutableArray.CreateBuilder<TItem>();
|
||||
|
||||
/// <summary>
|
||||
/// CancellationToken controlling the next batch of items to execute.
|
||||
/// </summary>
|
||||
private CancellationToken _nextBatchCancellationToken;
|
||||
|
||||
/// <summary>
|
||||
/// Used if <see cref="_equalityComparer"/> is present to ensure only unique items are added to <see
|
||||
/// cref="_nextBatch"/>.
|
||||
/// </summary>
|
||||
private readonly HashSet<TItem> _uniqueItems;
|
||||
|
||||
/// <summary>
|
||||
/// Task kicked off to do the next batch of processing of <see cref="_nextBatch"/>. These
|
||||
/// tasks form a chain so that the next task only processes when the previous one completes.
|
||||
/// </summary>
|
||||
private Task<TResult?> _updateTask = Task.FromResult(default(TResult));
|
||||
|
||||
/// <summary>
|
||||
/// Whether or not there is an existing task in flight that will process the current batch
|
||||
/// of <see cref="_nextBatch"/>. If there is an existing in flight task, we don't need to
|
||||
/// kick off a new one if we receive more work before it runs.
|
||||
/// </summary>
|
||||
private bool _taskInFlight = false;
|
||||
|
||||
#endregion
|
||||
|
||||
public AsyncBatchingWorkQueue(
|
||||
TimeSpan delay,
|
||||
Func<ImmutableArray<TItem>, CancellationToken, ValueTask<TResult>> processBatchAsync,
|
||||
IEqualityComparer<TItem>? equalityComparer,
|
||||
CancellationToken cancellationToken)
|
||||
{
|
||||
_delay = delay;
|
||||
_processBatchAsync = processBatchAsync;
|
||||
_equalityComparer = equalityComparer;
|
||||
_entireQueueCancellationToken = cancellationToken;
|
||||
|
||||
_uniqueItems = new HashSet<TItem>(equalityComparer);
|
||||
|
||||
// Combine with the queue cancellation token so that any batch is controlled by that token as well.
|
||||
_cancellationSeries = new CancellationSeries(_entireQueueCancellationToken);
|
||||
CancelExistingWork();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Cancels any outstanding work in this queue. Work that has not yet started will never run. Work that is in
|
||||
/// progress will request cancellation in a standard best effort fashion.
|
||||
/// </summary>
|
||||
public void CancelExistingWork()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
// Cancel out the current executing batch, and create a new token for the next batch.
|
||||
_nextBatchCancellationToken = _cancellationSeries.CreateNext();
|
||||
|
||||
// Clear out the existing items that haven't run yet. There is no point keeping them around now.
|
||||
_nextBatch.Clear();
|
||||
_uniqueItems.Clear();
|
||||
}
|
||||
}
|
||||
|
||||
public void AddWork(TItem item, bool cancelExistingWork = false)
|
||||
{
|
||||
using var _ = ArrayBuilderPool<TItem>.GetPooledObject(out var items);
|
||||
items.Add(item);
|
||||
|
||||
AddWork(items, cancelExistingWork);
|
||||
}
|
||||
|
||||
public void AddWork(IEnumerable<TItem> items, bool cancelExistingWork = false)
|
||||
{
|
||||
// Don't do any more work if we've been asked to shutdown.
|
||||
if (_entireQueueCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
lock (_gate)
|
||||
{
|
||||
// if we were asked to cancel the prior set of items, do so now.
|
||||
if (cancelExistingWork)
|
||||
{
|
||||
CancelExistingWork();
|
||||
}
|
||||
|
||||
// add our work to the set we'll process in the next batch.
|
||||
AddItemsToBatch(items);
|
||||
|
||||
if (!_taskInFlight)
|
||||
{
|
||||
// No in-flight task. Kick one off to process these messages a second from now.
|
||||
// We always attach the task to the previous one so that notifications to the ui
|
||||
// follow the same order as the notification the OOP server sent to us.
|
||||
_updateTask = ContinueAfterDelayAsync(_updateTask);
|
||||
_taskInFlight = true;
|
||||
}
|
||||
}
|
||||
|
||||
return;
|
||||
|
||||
void AddItemsToBatch(IEnumerable<TItem> items)
|
||||
{
|
||||
// no equality comparer. We want to process all items.
|
||||
if (_equalityComparer == null)
|
||||
{
|
||||
_nextBatch.AddRange(items);
|
||||
return;
|
||||
}
|
||||
|
||||
// We're deduping items. Only add the item if it's the first time we've seen it.
|
||||
foreach (var item in items)
|
||||
{
|
||||
if (_uniqueItems.Add(item))
|
||||
{
|
||||
_nextBatch.Add(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async Task<TResult?> ContinueAfterDelayAsync(Task lastTask)
|
||||
{
|
||||
// Await the previous item in the task chain in a non-throwing fashion. Regardless of whether that last
|
||||
// task completed successfully or not, we want to move onto the next batch.
|
||||
await lastTask.NoThrowAwaitable(captureContext: false);
|
||||
|
||||
// If we were asked to shutdown, immediately transition to the canceled state without doing any more work.
|
||||
_entireQueueCancellationToken.ThrowIfCancellationRequested();
|
||||
|
||||
// Ensure that we always yield the current thread this is necessary for correctness as we are called
|
||||
// inside a lock that _taskInFlight to true. We must ensure that the work to process the next batch
|
||||
// must be on another thread that runs afterwards, can only grab the thread once we release it and will
|
||||
// then reset that bool back to false
|
||||
await Task.Yield().ConfigureAwait(false);
|
||||
await Task.Delay(_delay, _entireQueueCancellationToken).ConfigureAwait(false);
|
||||
return await ProcessNextBatchAsync().ConfigureAwait(false);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Waits until the current batch of work completes and returns the last value successfully computed from <see
|
||||
/// cref="_processBatchAsync"/>. If the last <see cref="_processBatchAsync"/> canceled or failed, then a
|
||||
/// corresponding canceled or faulted task will be returned that propagates that outwards.
|
||||
/// </summary>
|
||||
public Task<TResult?> WaitUntilCurrentBatchCompletesAsync()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
#pragma warning disable VSTHRD003 // Avoid awaiting foreign Tasks
|
||||
return _updateTask;
|
||||
#pragma warning restore VSTHRD003 // Avoid awaiting foreign Tasks
|
||||
}
|
||||
}
|
||||
|
||||
private async ValueTask<TResult?> ProcessNextBatchAsync()
|
||||
{
|
||||
_entireQueueCancellationToken.ThrowIfCancellationRequested();
|
||||
try
|
||||
{
|
||||
var (nextBatch, batchCancellationToken) = GetNextBatchAndResetQueue();
|
||||
|
||||
// We may have no items if the entire batch was canceled (and no new work was added).
|
||||
if (nextBatch.IsEmpty)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var batchResultTask = _processBatchAsync(nextBatch, batchCancellationToken).Preserve();
|
||||
await batchResultTask.NoThrowAwaitable(false);
|
||||
|
||||
if (batchResultTask.IsCompletedSuccessfully)
|
||||
{
|
||||
// The VS threading library analyzers warn here because we're accessing Result
|
||||
// directly, which can be block. However, this is safe because we've already awaited
|
||||
// the task and verified that it completed successfully.
|
||||
#pragma warning disable VSTHRD103
|
||||
return batchResultTask.Result;
|
||||
#pragma warning restore VSTHRD103
|
||||
}
|
||||
else if (batchResultTask.IsCanceled && !_entireQueueCancellationToken.IsCancellationRequested)
|
||||
{
|
||||
// Don't bubble up cancellation to the queue for the nested batch cancellation. Just because we decided
|
||||
// to cancel this batch isn't something that should stop processing further batches.
|
||||
return default;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Realize the completed result to force the exception to be thrown.
|
||||
batchResultTask.VerifyCompleted();
|
||||
|
||||
return Assumed.Unreachable<TResult?>();
|
||||
}
|
||||
}
|
||||
catch (OperationCanceledException)
|
||||
{
|
||||
// Silently continue to allow the next batch to be processed.
|
||||
}
|
||||
|
||||
return Assumed.Unreachable<TResult?>();
|
||||
}
|
||||
|
||||
private (ImmutableArray<TItem> items, CancellationToken batchCancellationToken) GetNextBatchAndResetQueue()
|
||||
{
|
||||
lock (_gate)
|
||||
{
|
||||
var nextBatch = _nextBatch.ToImmutable();
|
||||
|
||||
// mark there being no existing update task so that the next OOP notification will
|
||||
// kick one off.
|
||||
_nextBatch.Clear();
|
||||
_uniqueItems.Clear();
|
||||
_taskInFlight = false;
|
||||
|
||||
return (nextBatch, _nextBatchCancellationToken);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -2,6 +2,7 @@
|
|||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
using System.Diagnostics;
|
||||
using System.Diagnostics.CodeAnalysis;
|
||||
using System.Runtime.CompilerServices;
|
||||
|
||||
|
@ -9,14 +10,35 @@ namespace Microsoft.AspNetCore.Razor;
|
|||
|
||||
internal static class Assumed
|
||||
{
|
||||
public static void False(
|
||||
[DoesNotReturnIf(true)] bool condition,
|
||||
[CallerFilePath] string? path = null,
|
||||
[CallerLineNumber] int line = 0)
|
||||
{
|
||||
if (condition)
|
||||
{
|
||||
ThrowInvalidOperation(SR.Expected_condition_to_be_false, path, line);
|
||||
}
|
||||
}
|
||||
|
||||
public static void True(
|
||||
[DoesNotReturnIf(false)] bool condition,
|
||||
[CallerFilePath] string? path = null,
|
||||
[CallerLineNumber] int line = 0)
|
||||
{
|
||||
if (!condition)
|
||||
{
|
||||
ThrowInvalidOperation(SR.Expected_condition_to_be_true, path, line);
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Can be called at points that are assumed to be unreachable at runtime.
|
||||
/// </summary>
|
||||
/// <exception cref="InvalidOperationException"/>
|
||||
[DoesNotReturn]
|
||||
public static void Unreachable([CallerFilePath] string? path = null, [CallerLineNumber] int line = 0)
|
||||
=> throw new InvalidOperationException(
|
||||
SR.FormatThis_program_location_is_thought_to_be_unreachable_File_0_Line_1(path, line));
|
||||
=> ThrowInvalidOperation(SR.This_program_location_is_thought_to_be_unreachable, path, line);
|
||||
|
||||
/// <summary>
|
||||
/// Can be called at points that are assumed to be unreachable at runtime.
|
||||
|
@ -24,6 +46,16 @@ internal static class Assumed
|
|||
/// <exception cref="InvalidOperationException"/>
|
||||
[DoesNotReturn]
|
||||
public static T Unreachable<T>([CallerFilePath] string? path = null, [CallerLineNumber] int line = 0)
|
||||
=> throw new InvalidOperationException(
|
||||
SR.FormatThis_program_location_is_thought_to_be_unreachable_File_0_Line_1(path, line));
|
||||
{
|
||||
ThrowInvalidOperation(SR.This_program_location_is_thought_to_be_unreachable, path, line);
|
||||
return default;
|
||||
}
|
||||
|
||||
[DebuggerHidden]
|
||||
[DoesNotReturn]
|
||||
[MethodImpl(MethodImplOptions.NoInlining)]
|
||||
private static void ThrowInvalidOperation(string message, string? path, int line)
|
||||
{
|
||||
throw new InvalidOperationException(message);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,11 +126,20 @@
|
|||
<data name="Destination_is_too_short" xml:space="preserve">
|
||||
<value>Destination is too short.</value>
|
||||
</data>
|
||||
<data name="Expected_condition_to_be_false" xml:space="preserve">
|
||||
<value>Expected condition to be false.</value>
|
||||
</data>
|
||||
<data name="Expected_condition_to_be_true" xml:space="preserve">
|
||||
<value>Expected condition to be true.</value>
|
||||
</data>
|
||||
<data name="File_0_Line_1" xml:space="preserve">
|
||||
<value> File='{0}', Line={1}</value>
|
||||
</data>
|
||||
<data name="Non_negative_number_required" xml:space="preserve">
|
||||
<value>Non-negative number required.</value>
|
||||
</data>
|
||||
<data name="This_program_location_is_thought_to_be_unreachable_File_0_Line_1" xml:space="preserve">
|
||||
<value>This program location is thought to be unreachable. File='{0}', Line={1}</value>
|
||||
<data name="This_program_location_is_thought_to_be_unreachable" xml:space="preserve">
|
||||
<value>This program location is thought to be unreachable.</value>
|
||||
</data>
|
||||
<data name="Unsupported_type_0" xml:space="preserve">
|
||||
<value>Unsupported type: '{0}'.</value>
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">Cíl je příliš krátký.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">Vyžaduje se nezáporné číslo.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">Toto umístění programu se považuje za nedostupné. File={0}, Line={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">Das Ziel ist zu kurz.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">Nicht negative Zahl erforderlich.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">Dieser Programmspeicherort wird als nicht erreichbar betrachtet. Datei="{0}", Zeile={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">El destino es demasiado corto.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">Se requiere un número no negativo.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">Se considera que esta ubicación del programa es inaccesible. Archivo=''{0}'', Línea={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">La destination est trop courte.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">Nombre non négatif obligatoire.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">Cet emplacement du programme est considéré comme inaccessible. File='{0}', Ligne={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">La destinazione è troppo breve.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">Numero non negativo obbligatorio.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">Questo percorso del programma è considerato irraggiungibile. File='{0}', Riga={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">宛先が短すぎます。</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">負でない数値が必要です。</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">このプログラムの場所には到達できないと考えられます。ファイル='{0}'、行={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">대상이 너무 짧습니다.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">음수가 아닌 수가 필요합니다.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">이 프로그램 위치에 연결할 수 없는 것으로 간주합니다. File=’{0}’, Line={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">Miejsce docelowe jest zbyt krótkie.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">Wymagana jest liczba nieujemna.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">Ta lokalizacja programu jest uważana za nieosiągalną. Plik=„{0}”, Linia={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">O destino é muito curto.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">É necessário um número não negativo.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">Este local do programa é considerado inacessível. Arquivo='{0}', Linha={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">Имя назначения слишком короткое.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">Требуется неотрицательное число.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">Похоже, что это расположение программы является недоступным. Файл="{0}", строка={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">Hedef çok kısa.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">Negatif olmayan sayı gerekiyor.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">Bu program konumu erişilemez olarak görülüyor. Dosya='{0}', Satır={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">目标太短。</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">需要提供非负数。</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">此程序位置被视为无法访问。File='{0}',Line={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -17,14 +17,29 @@
|
|||
<target state="translated">目的地太短。</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_false">
|
||||
<source>Expected condition to be false.</source>
|
||||
<target state="new">Expected condition to be false.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Expected_condition_to_be_true">
|
||||
<source>Expected condition to be true.</source>
|
||||
<target state="new">Expected condition to be true.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="File_0_Line_1">
|
||||
<source> File='{0}', Line={1}</source>
|
||||
<target state="new"> File='{0}', Line={1}</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Non_negative_number_required">
|
||||
<source>Non-negative number required.</source>
|
||||
<target state="translated">需要非負數。</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable_File_0_Line_1">
|
||||
<source>This program location is thought to be unreachable. File='{0}', Line={1}</source>
|
||||
<target state="translated">此程式位置被認為無法連接。File='{0}', Line={1}</target>
|
||||
<trans-unit id="This_program_location_is_thought_to_be_unreachable">
|
||||
<source>This program location is thought to be unreachable.</source>
|
||||
<target state="new">This program location is thought to be unreachable.</target>
|
||||
<note />
|
||||
</trans-unit>
|
||||
<trans-unit id="Unsupported_type_0">
|
||||
|
|
|
@ -0,0 +1,21 @@
|
|||
// Copyright (c) .NET Foundation. All rights reserved.
|
||||
// Licensed under the MIT license. See License.txt in the project root for license information.
|
||||
|
||||
using System;
|
||||
|
||||
namespace Microsoft.AspNetCore.Razor;
|
||||
|
||||
/// <summary>
|
||||
/// Explicitly indicates result is void
|
||||
/// </summary>
|
||||
internal readonly struct VoidResult : IEquatable<VoidResult>
|
||||
{
|
||||
public override bool Equals(object? obj)
|
||||
=> obj is VoidResult;
|
||||
|
||||
public override int GetHashCode()
|
||||
=> 0;
|
||||
|
||||
public bool Equals(VoidResult other)
|
||||
=> true;
|
||||
}
|
Загрузка…
Ссылка в новой задаче