|
// Licensed to the .NET Foundation under one or more agreements.
// The .NET Foundation licenses this file to you under the MIT license.
// See the LICENSE file in the project root for more information.
#nullable disable
using System;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.CodeAnalysis.Test.Utilities;
using Roslyn.Test.Utilities;
using Roslyn.Utilities;
using Xunit;
namespace Microsoft.CodeAnalysis.UnitTests
{
[Trait(Traits.Feature, Traits.Features.AsyncLazy)]
public class AsyncLazyTests
{
[Fact]
public void CancellationDuringInlinedComputationFromGetValueStillCachesResult()
{
CancellationDuringInlinedComputationFromGetValueOrGetValueAsyncStillCachesResultCore((lazy, ct) => lazy.GetValue(ct), includeSynchronousComputation: true);
CancellationDuringInlinedComputationFromGetValueOrGetValueAsyncStillCachesResultCore((lazy, ct) => lazy.GetValue(ct), includeSynchronousComputation: false);
}
private static void CancellationDuringInlinedComputationFromGetValueOrGetValueAsyncStillCachesResultCore(Func<AsyncLazy<object>, CancellationToken, object> doGetValue, bool includeSynchronousComputation)
{
var computations = 0;
var requestCancellationTokenSource = new CancellationTokenSource();
object createdObject = null;
Func<CancellationToken, object> synchronousComputation = c =>
{
Interlocked.Increment(ref computations);
// We do not want to ever use the cancellation token that we are passed to this
// computation. Rather, we will ignore it but cancel any request that is
// outstanding.
requestCancellationTokenSource.Cancel();
createdObject = new object();
return createdObject;
};
var lazy = new AsyncLazy<object>(
c => Task.FromResult(synchronousComputation(c)),
includeSynchronousComputation ? synchronousComputation : null,
cacheResult: true);
var thrownException = Assert.Throws<OperationCanceledException>(() =>
{
// Do a first request. Even though we will get a cancellation during the evaluation,
// since we handed a result back, that result must be cached.
doGetValue(lazy, requestCancellationTokenSource.Token);
});
// And a second request. We'll let this one complete normally.
var secondRequestResult = doGetValue(lazy, CancellationToken.None);
// We should have gotten the same cached result, and we should have only computed once.
Assert.Same(createdObject, secondRequestResult);
Assert.Equal(1, computations);
}
[Fact]
public void SynchronousRequestShouldCacheValueWithAsynchronousComputeFunction()
{
var lazy = new AsyncLazy<object>(c => Task.FromResult(new object()), cacheResult: true);
var firstRequestResult = lazy.GetValue(CancellationToken.None);
var secondRequestResult = lazy.GetValue(CancellationToken.None);
Assert.Same(secondRequestResult, firstRequestResult);
}
[Theory]
[CombinatorialData]
public async Task AwaitingProducesCorrectException(bool producerAsync, bool consumerAsync)
{
var exception = new ArgumentException();
Func<CancellationToken, Task<object>> asynchronousComputeFunction =
async cancellationToken =>
{
await Task.Yield();
throw exception;
};
Func<CancellationToken, object> synchronousComputeFunction =
cancellationToken =>
{
throw exception;
};
var lazy = producerAsync
? new AsyncLazy<object>(asynchronousComputeFunction, cacheResult: true)
: new AsyncLazy<object>(asynchronousComputeFunction, synchronousComputeFunction, cacheResult: true);
var actual = consumerAsync
? await Assert.ThrowsAsync<ArgumentException>(async () => await lazy.GetValueAsync(CancellationToken.None))
: Assert.Throws<ArgumentException>(() => lazy.GetValue(CancellationToken.None));
Assert.Same(exception, actual);
}
}
}
|