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:
Dustin Campbell 2024-03-20 11:07:37 -07:00
Родитель d4a6aaafa0
Коммит 530fd6d13d
24 изменённых файлов: 793 добавлений и 53 удалений

Просмотреть файл

@ -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;
}