protected override async ValueTask <IComputed <T> > ComputeAsync( InterceptedInput input, IComputed <T>?existing, CancellationToken cancellationToken) { var tag = VersionGenerator.Next(); var computed = CreateComputed(input, tag); try { using var _ = Computed.ChangeCurrent(computed); var result = input.InvokeOriginalFunction(cancellationToken); if (input.Method.ReturnsValueTask) { var output = await((ValueTask <T>)result).ConfigureAwait(false); computed.TrySetOutput(output); } else { var output = await((Task <T>)result).ConfigureAwait(false); computed.TrySetOutput(output); } } catch (OperationCanceledException) { throw; } catch (Exception e) { computed.TrySetOutput(Result.Error <T>(e)); // Weird case: if the output is already set, all we can // is to ignore the exception we've just caught; // throwing it further will probably make it just worse, // since the the caller have to take this scenario into acc. } return(computed); }
protected override async ValueTask <IComputed <T> > ComputeAsync( InterceptedInput input, IComputed <T>?cached, CancellationToken cancellationToken) { var tag = LTagGenerator.Next(); var method = Method; var output = new Computed <InterceptedInput, T>(method.Options, input, tag); try { using var _ = Computed.ChangeCurrent(output); var resultTask = input.InvokeOriginalFunction(cancellationToken); if (method.ReturnsComputed) { if (method.ReturnsValueTask) { var task = (ValueTask <IComputed <T> >)resultTask; await task.ConfigureAwait(false); // output == task.Result here, so no need to call output.TrySetOutput(...) } else { var task = (Task <IComputed <T> >)resultTask; await task.ConfigureAwait(false); // output == task.Result here, so no need to call output.TrySetOutput(...) } } else { if (method.ReturnsValueTask) { var task = (ValueTask <T>)resultTask; var value = await task.ConfigureAwait(false); output.TrySetOutput(value !); } else { var task = (Task <T>)resultTask; var value = await task.ConfigureAwait(false); output.TrySetOutput(value !); } } } catch (OperationCanceledException) { throw; } catch (Exception e) { output.TrySetOutput(Result.Error <T>(e)); // Weird case: if the output is already set, all we can // is to ignore the exception we've just caught; // throwing it further will probably make it just worse, // since the the caller have to take this scenario into acc. } return(output); }
// Protected methods protected static void SetReturnValue(InterceptedInput input, Result <TOut> output) { if (input.Method.ReturnsValueTask) { input.Invocation.ReturnValue = output.IsValue(out var v) ? ValueTaskEx.FromResult(v) : ValueTaskEx.FromException <TOut>(output.Error !); } else { input.Invocation.ReturnValue = output.IsValue(out var v) ? Task.FromResult(v) : Task.FromException <TOut>(output.Error !); } }
// Protected methods protected static void SetReturnValue(InterceptedInput input, Result <TOut> output) { if (input.Method.ReturnsValueTask) { input.Invocation.ReturnValue = // ReSharper disable once HeapView.BoxingAllocation output.IsValue(out var v) ? ValueTaskEx.FromResult(v) : ValueTaskEx.FromException <TOut>(output.Error !); } else { input.Invocation.ReturnValue = output.IsValue(out var v) ? Task.FromResult(v) : Task.FromException <TOut>(output.Error !); } }
public override async Task <T> InvokeAndStripAsync( InterceptedInput input, IComputed?usedBy, ComputeContext?context, CancellationToken cancellationToken = default) { context ??= ComputeContext.Current; ResultBox <T>?output; // Read-Lock-RetryRead-Compute-Store pattern var computed = TryGetExisting(input); if (computed != null) { output = await computed.TryUseExistingAsync(context, usedBy, cancellationToken) .ConfigureAwait(false); if (output != null) { return(output.Value); } } using var @lock = await Locks.LockAsync(input, cancellationToken).ConfigureAwait(false); computed = TryGetExisting(input); if (computed != null) { output = await computed.TryUseExistingAsync(context, usedBy, cancellationToken) .ConfigureAwait(false); if (output != null) { return(output.Value); } } computed = (IAsyncComputed <T>) await ComputeAsync(input, computed, cancellationToken) .ConfigureAwait(false); output = computed.Output; // RenewTimeouts isn't called yet, so it's ok computed.UseNew(context, usedBy); return(output !.Value); }
protected override IComputed <T> CreateComputed(InterceptedInput input, LTag tag) => new Computed <T>(Options, input, tag);
protected abstract IComputed <T> CreateComputed(InterceptedInput input, LTag tag);
new protected IAsyncComputed <T>?TryGetExisting(InterceptedInput input) { var computed = ComputedRegistry.Instance.TryGet(input); return(computed as IAsyncComputed <T>); }