UseMiddleware resolves InvokeAsync in addition to Invoke

This commit is contained in:
John Luo 2017-03-25 23:42:14 -07:00
parent b3b846c27e
commit 2bdbbbf41d
5 changed files with 177 additions and 100 deletions

View File

@ -17,7 +17,8 @@ namespace Microsoft.AspNetCore.Builder
/// </summary> /// </summary>
public static class UseMiddlewareExtensions public static class UseMiddlewareExtensions
{ {
private const string InvokeMethodName = "Invoke"; internal const string InvokeMethodName = "Invoke";
internal const string InvokeAsyncMethodName = "InvokeAsync";
private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static); private static readonly MethodInfo GetServiceInfo = typeof(UseMiddlewareExtensions).GetMethod(nameof(GetService), BindingFlags.NonPublic | BindingFlags.Static);
@ -44,7 +45,7 @@ namespace Microsoft.AspNetCore.Builder
{ {
if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo())) if (typeof(IMiddleware).GetTypeInfo().IsAssignableFrom(middleware.GetTypeInfo()))
{ {
// IMiddleware doesn't support passing args directly since it's // IMiddleware doesn't support passing args directly since it's
// activated from the container // activated from the container
if (args.Length > 0) if (args.Length > 0)
{ {
@ -58,27 +59,31 @@ namespace Microsoft.AspNetCore.Builder
return app.Use(next => return app.Use(next =>
{ {
var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public); var methods = middleware.GetMethods(BindingFlags.Instance | BindingFlags.Public);
var invokeMethods = methods.Where(m => string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)).ToArray(); var invokeMethods = methods.Where(m =>
string.Equals(m.Name, InvokeMethodName, StringComparison.Ordinal)
|| string.Equals(m.Name, InvokeAsyncMethodName, StringComparison.Ordinal)
).ToArray();
if (invokeMethods.Length > 1) if (invokeMethods.Length > 1)
{ {
throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName)); throw new InvalidOperationException(Resources.FormatException_UseMiddleMutlipleInvokes(InvokeMethodName, InvokeAsyncMethodName));
} }
if (invokeMethods.Length == 0) if (invokeMethods.Length == 0)
{ {
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName)); throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoInvokeMethod(InvokeMethodName, InvokeAsyncMethodName));
} }
var methodinfo = invokeMethods[0]; var methodinfo = invokeMethods[0];
if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType)) if (!typeof(Task).IsAssignableFrom(methodinfo.ReturnType))
{ {
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, nameof(Task))); throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNonTaskReturnType(InvokeMethodName, InvokeAsyncMethodName, nameof(Task)));
} }
var parameters = methodinfo.GetParameters(); var parameters = methodinfo.GetParameters();
if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext)) if (parameters.Length == 0 || parameters[0].ParameterType != typeof(HttpContext))
{ {
throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, nameof(HttpContext))); throw new InvalidOperationException(Resources.FormatException_UseMiddlewareNoParameters(InvokeMethodName, InvokeAsyncMethodName, nameof(HttpContext)));
} }
var ctorArgs = new object[args.Length + 1]; var ctorArgs = new object[args.Length + 1];
@ -127,7 +132,7 @@ namespace Microsoft.AspNetCore.Builder
try try
{ {
await middleware.Invoke(context, next); await middleware.InvokeAsync(context, next);
} }
finally finally
{ {
@ -140,12 +145,12 @@ namespace Microsoft.AspNetCore.Builder
private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters) private static Func<T, HttpContext, IServiceProvider, Task> Compile<T>(MethodInfo methodinfo, ParameterInfo[] parameters)
{ {
// If we call something like // If we call something like
// //
// public class Middleware // public class Middleware
// { // {
// public Task Invoke(HttpContext context, ILoggerFactory loggeryFactory) // public Task Invoke(HttpContext context, ILoggerFactory loggeryFactory)
// { // {
// //
// } // }
// } // }
// //

View File

@ -1,10 +1,6 @@
// Copyright (c) .NET Foundation. All rights reserved. // Copyright (c) .NET Foundation. All rights reserved.
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. // Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information.
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks; using System.Threading.Tasks;
namespace Microsoft.AspNetCore.Http namespace Microsoft.AspNetCore.Http
@ -20,6 +16,6 @@ namespace Microsoft.AspNetCore.Http
/// <param name="context">The <see cref="HttpContext"/> for the current request.</param> /// <param name="context">The <see cref="HttpContext"/> for the current request.</param>
/// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param> /// <param name="next">The delegate representing the remaining middleware in the request pipeline.</param>
/// <returns>A <see cref="Task"/> that represents the execution of this middleware.</returns> /// <returns>A <see cref="Task"/> that represents the execution of this middleware.</returns>
Task Invoke(HttpContext context, RequestDelegate next); Task InvokeAsync(HttpContext context, RequestDelegate next);
} }
} }

View File

@ -37,9 +37,9 @@ namespace Microsoft.AspNetCore.Http.Abstractions
/// <summary> /// <summary>
/// No public '{0}' method found. /// No public '{0}' method found.
/// </summary> /// </summary>
internal static string FormatException_UseMiddlewareNoInvokeMethod(object p0) internal static string FormatException_UseMiddlewareNoInvokeMethod(object p0, object p1)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoInvokeMethod"), p0); return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoInvokeMethod"), p0, p1);
} }
/// <summary> /// <summary>
@ -53,9 +53,9 @@ namespace Microsoft.AspNetCore.Http.Abstractions
/// <summary> /// <summary>
/// '{0}' does not return an object of type '{1}'. /// '{0}' does not return an object of type '{1}'.
/// </summary> /// </summary>
internal static string FormatException_UseMiddlewareNonTaskReturnType(object p0, object p1) internal static string FormatException_UseMiddlewareNonTaskReturnType(object p0, object p1, object p2)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNonTaskReturnType"), p0, p1); return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNonTaskReturnType"), p0, p1, p2);
} }
/// <summary> /// <summary>
@ -69,9 +69,9 @@ namespace Microsoft.AspNetCore.Http.Abstractions
/// <summary> /// <summary>
/// The '{0}' method's first argument must be of type '{1}'. /// The '{0}' method's first argument must be of type '{1}'.
/// </summary> /// </summary>
internal static string FormatException_UseMiddlewareNoParameters(object p0, object p1) internal static string FormatException_UseMiddlewareNoParameters(object p0, object p1, object p2)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoParameters"), p0, p1); return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddlewareNoParameters"), p0, p1, p2);
} }
/// <summary> /// <summary>
@ -85,9 +85,9 @@ namespace Microsoft.AspNetCore.Http.Abstractions
/// <summary> /// <summary>
/// Multiple public '{0}' methods are available. /// Multiple public '{0}' methods are available.
/// </summary> /// </summary>
internal static string FormatException_UseMiddleMutlipleInvokes(object p0) internal static string FormatException_UseMiddleMutlipleInvokes(object p0, object p1)
{ {
return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddleMutlipleInvokes"), p0); return string.Format(CultureInfo.CurrentCulture, GetString("Exception_UseMiddleMutlipleInvokes"), p0, p1);
} }
/// <summary> /// <summary>

View File

@ -121,16 +121,16 @@
<value>'{0}' is not available.</value> <value>'{0}' is not available.</value>
</data> </data>
<data name="Exception_UseMiddlewareNoInvokeMethod" xml:space="preserve"> <data name="Exception_UseMiddlewareNoInvokeMethod" xml:space="preserve">
<value>No public '{0}' method found.</value> <value>No public '{0}' or '{1}' method found.</value>
</data> </data>
<data name="Exception_UseMiddlewareNonTaskReturnType" xml:space="preserve"> <data name="Exception_UseMiddlewareNonTaskReturnType" xml:space="preserve">
<value>'{0}' does not return an object of type '{1}'.</value> <value>'{0}' or '{1}' does not return an object of type '{2}'.</value>
</data> </data>
<data name="Exception_UseMiddlewareNoParameters" xml:space="preserve"> <data name="Exception_UseMiddlewareNoParameters" xml:space="preserve">
<value>The '{0}' method's first argument must be of type '{1}'.</value> <value>The '{0}' or '{1}' method's first argument must be of type '{2}'.</value>
</data> </data>
<data name="Exception_UseMiddleMutlipleInvokes" xml:space="preserve"> <data name="Exception_UseMiddleMutlipleInvokes" xml:space="preserve">
<value>Multiple public '{0}' methods are available.</value> <value>Multiple public '{0}' or '{1}' methods are available.</value>
</data> </data>
<data name="Exception_PathMustStartWithSlash" xml:space="preserve"> <data name="Exception_PathMustStartWithSlash" xml:space="preserve">
<value>The path in '{0}' must start with '/'.</value> <value>The path in '{0}' must start with '/'.</value>

View File

@ -7,6 +7,7 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Builder.Internal; using Microsoft.AspNetCore.Builder.Internal;
using Microsoft.AspNetCore.Http.Abstractions; using Microsoft.AspNetCore.Http.Abstractions;
using Microsoft.Extensions.Internal;
using Xunit; using Xunit;
namespace Microsoft.AspNetCore.Http namespace Microsoft.AspNetCore.Http
@ -16,60 +17,137 @@ namespace Microsoft.AspNetCore.Http
[Fact] [Fact]
public void UseMiddleware_WithNoParameters_ThrowsException() public void UseMiddleware_WithNoParameters_ThrowsException()
{ {
var mockServiceProvider = new DummyServiceProvider(); var builder = new ApplicationBuilder(new DummyServiceProvider());
var builder = new ApplicationBuilder(mockServiceProvider);
builder.UseMiddleware(typeof(MiddlewareNoParametersStub)); builder.UseMiddleware(typeof(MiddlewareNoParametersStub));
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build()); var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Equal(Resources.FormatException_UseMiddlewareNoParameters("Invoke", nameof(HttpContext)), exception.Message); Assert.Equal(
Resources.FormatException_UseMiddlewareNoParameters(
UseMiddlewareExtensions.InvokeMethodName,
UseMiddlewareExtensions.InvokeAsyncMethodName,
nameof(HttpContext)),
exception.Message);
}
[Fact]
public void UseMiddleware_AsyncWithNoParameters_ThrowsException()
{
var builder = new ApplicationBuilder(new DummyServiceProvider());
builder.UseMiddleware(typeof(MiddlewareAsyncNoParametersStub));
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Equal(
Resources.FormatException_UseMiddlewareNoParameters(
UseMiddlewareExtensions.InvokeMethodName,
UseMiddlewareExtensions.InvokeAsyncMethodName,
nameof(HttpContext)),
exception.Message);
} }
[Fact] [Fact]
public void UseMiddleware_NonTaskReturnType_ThrowsException() public void UseMiddleware_NonTaskReturnType_ThrowsException()
{ {
var mockServiceProvider = new DummyServiceProvider(); var builder = new ApplicationBuilder(new DummyServiceProvider());
var builder = new ApplicationBuilder(mockServiceProvider);
builder.UseMiddleware(typeof(MiddlewareNonTaskReturnStub)); builder.UseMiddleware(typeof(MiddlewareNonTaskReturnStub));
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build()); var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Equal(Resources.FormatException_UseMiddlewareNonTaskReturnType("Invoke", nameof(Task)), exception.Message);
Assert.Equal(
Resources.FormatException_UseMiddlewareNonTaskReturnType(
UseMiddlewareExtensions.InvokeMethodName,
UseMiddlewareExtensions.InvokeAsyncMethodName,
nameof(Task)),
exception.Message);
} }
[Fact] [Fact]
public void UseMiddleware_NoInvokeMethod_ThrowsException() public void UseMiddleware_AsyncNonTaskReturnType_ThrowsException()
{ {
var mockServiceProvider = new DummyServiceProvider(); var builder = new ApplicationBuilder(new DummyServiceProvider());
var builder = new ApplicationBuilder(mockServiceProvider); builder.UseMiddleware(typeof(MiddlewareAsyncNonTaskReturnStub));
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Equal(
Resources.FormatException_UseMiddlewareNonTaskReturnType(
UseMiddlewareExtensions.InvokeMethodName,
UseMiddlewareExtensions.InvokeAsyncMethodName,
nameof(Task)),
exception.Message);
}
[Fact]
public void UseMiddleware_NoInvokeOrInvokeAsyncMethod_ThrowsException()
{
var builder = new ApplicationBuilder(new DummyServiceProvider());
builder.UseMiddleware(typeof(MiddlewareNoInvokeStub)); builder.UseMiddleware(typeof(MiddlewareNoInvokeStub));
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build()); var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Equal(Resources.FormatException_UseMiddlewareNoInvokeMethod("Invoke"), exception.Message);
Assert.Equal(
Resources.FormatException_UseMiddlewareNoInvokeMethod(
UseMiddlewareExtensions.InvokeMethodName,
UseMiddlewareExtensions.InvokeAsyncMethodName),
exception.Message);
} }
[Fact] [Fact]
public void UseMiddleware_MutlipleInvokeMethods_ThrowsException() public void UseMiddleware_MutlipleInvokeMethods_ThrowsException()
{ {
var mockServiceProvider = new DummyServiceProvider(); var builder = new ApplicationBuilder(new DummyServiceProvider());
var builder = new ApplicationBuilder(mockServiceProvider);
builder.UseMiddleware(typeof(MiddlewareMultipleInvokesStub)); builder.UseMiddleware(typeof(MiddlewareMultipleInvokesStub));
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build()); var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Equal(Resources.FormatException_UseMiddleMutlipleInvokes("Invoke"), exception.Message);
Assert.Equal(
Resources.FormatException_UseMiddleMutlipleInvokes(
UseMiddlewareExtensions.InvokeMethodName,
UseMiddlewareExtensions.InvokeAsyncMethodName),
exception.Message);
}
[Fact]
public void UseMiddleware_MutlipleInvokeAsyncMethods_ThrowsException()
{
var builder = new ApplicationBuilder(new DummyServiceProvider());
builder.UseMiddleware(typeof(MiddlewareMultipleInvokeAsyncStub));
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Equal(
Resources.FormatException_UseMiddleMutlipleInvokes(
UseMiddlewareExtensions.InvokeMethodName,
UseMiddlewareExtensions.InvokeAsyncMethodName),
exception.Message);
}
[Fact]
public void UseMiddleware_MutlipleInvokeAndInvokeAsyncMethods_ThrowsException()
{
var builder = new ApplicationBuilder(new DummyServiceProvider());
builder.UseMiddleware(typeof(MiddlewareMultipleInvokeAndInvokeAsyncStub));
var exception = Assert.Throws<InvalidOperationException>(() => builder.Build());
Assert.Equal(
Resources.FormatException_UseMiddleMutlipleInvokes(
UseMiddlewareExtensions.InvokeMethodName,
UseMiddlewareExtensions.InvokeAsyncMethodName),
exception.Message);
} }
[Fact] [Fact]
public async Task UseMiddleware_ThrowsIfArgCantBeResolvedFromContainer() public async Task UseMiddleware_ThrowsIfArgCantBeResolvedFromContainer()
{ {
var mockServiceProvider = new DummyServiceProvider(); var builder = new ApplicationBuilder(new DummyServiceProvider());
var builder = new ApplicationBuilder(mockServiceProvider);
builder.UseMiddleware(typeof(MiddlewareInjectInvokeNoService)); builder.UseMiddleware(typeof(MiddlewareInjectInvokeNoService));
var app = builder.Build(); var app = builder.Build();
var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => app(new DefaultHttpContext())); var exception = await Assert.ThrowsAsync<InvalidOperationException>(() => app(new DefaultHttpContext()));
Assert.Equal(Resources.FormatException_InvokeMiddlewareNoService(typeof(object), typeof(MiddlewareInjectInvokeNoService)), exception.Message); Assert.Equal(
Resources.FormatException_InvokeMiddlewareNoService(
typeof(object),
typeof(MiddlewareInjectInvokeNoService)),
exception.Message);
} }
[Fact] [Fact]
public void UseMiddlewareWithInvokeArg() public void UseMiddlewareWithInvokeArg()
{ {
var mockServiceProvider = new DummyServiceProvider(); var builder = new ApplicationBuilder(new DummyServiceProvider());
var builder = new ApplicationBuilder(mockServiceProvider);
builder.UseMiddleware(typeof(MiddlewareInjectInvoke)); builder.UseMiddleware(typeof(MiddlewareInjectInvoke));
var app = builder.Build(); var app = builder.Build();
app(new DefaultHttpContext()); app(new DefaultHttpContext());
@ -126,7 +204,11 @@ namespace Microsoft.AspNetCore.Http
await app(context); await app(context);
}); });
Assert.Equal(Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(typeof(BadMiddlewareFactory), typeof(Middleware)), exception.Message); Assert.Equal(
Resources.FormatException_UseMiddlewareUnableToCreateMiddleware(
typeof(BadMiddlewareFactory),
typeof(Middleware)),
exception.Message);
} }
[Fact] [Fact]
@ -153,7 +235,7 @@ namespace Microsoft.AspNetCore.Http
public class Middleware : IMiddleware public class Middleware : IMiddleware
{ {
public async Task Invoke(HttpContext context, RequestDelegate next) public async Task InvokeAsync(HttpContext context, RequestDelegate next)
{ {
context.Items["before"] = true; context.Items["before"] = true;
await next(context); await next(context);
@ -180,15 +262,9 @@ namespace Microsoft.AspNetCore.Http
public class BadMiddlewareFactory : IMiddlewareFactory public class BadMiddlewareFactory : IMiddlewareFactory
{ {
public IMiddleware Create(Type middlewareType) public IMiddleware Create(Type middlewareType) => null;
{
return null;
}
public void Release(IMiddleware middleware) public void Release(IMiddleware middleware) { }
{
}
} }
private class DummyServiceProvider : IServiceProvider private class DummyServiceProvider : IServiceProvider
@ -214,9 +290,7 @@ namespace Microsoft.AspNetCore.Http
public class MiddlewareInjectWithOutAndRefParams public class MiddlewareInjectWithOutAndRefParams
{ {
public MiddlewareInjectWithOutAndRefParams(RequestDelegate next) public MiddlewareInjectWithOutAndRefParams(RequestDelegate next) { }
{
}
public Task Invoke(HttpContext context, ref IServiceProvider sp1, out IServiceProvider sp2) public Task Invoke(HttpContext context, ref IServiceProvider sp1, out IServiceProvider sp2)
{ {
@ -228,74 +302,76 @@ namespace Microsoft.AspNetCore.Http
private class MiddlewareInjectInvokeNoService private class MiddlewareInjectInvokeNoService
{ {
public MiddlewareInjectInvokeNoService(RequestDelegate next) public MiddlewareInjectInvokeNoService(RequestDelegate next) { }
{
}
public Task Invoke(HttpContext context, object value) public Task Invoke(HttpContext context, object value) => TaskCache.CompletedTask;
{
return Task.FromResult(0);
}
} }
private class MiddlewareInjectInvoke private class MiddlewareInjectInvoke
{ {
public MiddlewareInjectInvoke(RequestDelegate next) public MiddlewareInjectInvoke(RequestDelegate next) { }
{
}
public Task Invoke(HttpContext context, IServiceProvider provider) public Task Invoke(HttpContext context, IServiceProvider provider) => TaskCache.CompletedTask;
{
return Task.FromResult(0);
}
} }
private class MiddlewareNoParametersStub private class MiddlewareNoParametersStub
{ {
public MiddlewareNoParametersStub(RequestDelegate next) public MiddlewareNoParametersStub(RequestDelegate next) { }
{
}
public Task Invoke() public Task Invoke() => TaskCache.CompletedTask;
{ }
return Task.FromResult(0);
} private class MiddlewareAsyncNoParametersStub
{
public MiddlewareAsyncNoParametersStub(RequestDelegate next) { }
public Task InvokeAsync() => TaskCache.CompletedTask;
} }
private class MiddlewareNonTaskReturnStub private class MiddlewareNonTaskReturnStub
{ {
public MiddlewareNonTaskReturnStub(RequestDelegate next) public MiddlewareNonTaskReturnStub(RequestDelegate next) { }
{
}
public int Invoke() public int Invoke() => 0;
{ }
return 0;
} private class MiddlewareAsyncNonTaskReturnStub
{
public MiddlewareAsyncNonTaskReturnStub(RequestDelegate next) { }
public int InvokeAsync() => 0;
} }
private class MiddlewareNoInvokeStub private class MiddlewareNoInvokeStub
{ {
public MiddlewareNoInvokeStub(RequestDelegate next) public MiddlewareNoInvokeStub(RequestDelegate next) { }
{
}
} }
private class MiddlewareMultipleInvokesStub private class MiddlewareMultipleInvokesStub
{ {
public MiddlewareMultipleInvokesStub(RequestDelegate next) public MiddlewareMultipleInvokesStub(RequestDelegate next) { }
{
}
public Task Invoke(HttpContext context) public Task Invoke(HttpContext context) => TaskCache.CompletedTask;
{
return Task.FromResult(0);
}
public Task Invoke(HttpContext context, int i) public Task Invoke(HttpContext context, int i) => TaskCache.CompletedTask;
{ }
return Task.FromResult(0);
} private class MiddlewareMultipleInvokeAsyncStub
{
public MiddlewareMultipleInvokeAsyncStub(RequestDelegate next) { }
public Task InvokeAsync(HttpContext context) => TaskCache.CompletedTask;
public Task InvokeAsync(HttpContext context, int i) => TaskCache.CompletedTask;
}
private class MiddlewareMultipleInvokeAndInvokeAsyncStub
{
public MiddlewareMultipleInvokeAndInvokeAsyncStub(RequestDelegate next) { }
public Task Invoke(HttpContext context) => TaskCache.CompletedTask;
public Task InvokeAsync(HttpContext context) => TaskCache.CompletedTask;
} }
} }
} }