在我的 ASP.NET 5 应用程序中,我想将一些数据从 Azure 加载到 Startup.Configure 方法内的缓存中。 Azure SDK 专门公开异步方法。通常,调用异步方法是通过异步方法内的等待完成的,如下所示:
public async Task Configure(IApplicationBuilder app, IMemoryCache cache)
{
Data dataToCache = await DataSource.LoadDataAsync();
cache.Set("somekey", dataToCache);
// remainder of Configure method omitted for clarity
}
但是,ASP.NET 5 要求Configure 方法返回void。我可以使用 async void 方法,但我的理解是 async void 方法只应该用于事件处理程序(根据https://msdn.microsoft.com/en-us/magazine/jj991977.aspx)还有很多其他的)。
我认为更好的方法是调用异步函数而无需等待,对返回的任务调用 Wait,然后通过 Task.Results 属性缓存结果,如下所示:
public void Configure(IApplicationBuilder app, IMemoryCache cache)
{
Task<Data> loadDataTask = DataSource.LoadDataAsync();
loadDataTask.Wait();
cache.Set("somekey", loadDataTask.Result);
// remainder of Configure method omitted for clarity
}
Stephen Walther 在今年早些时候的博客文章中使用了类似的方法。然而,从该帖子中尚不清楚这是否被认为是可接受的做法。是吗?
如果这被认为是可接受的做法,那么我需要什么(如果有的话)错误处理?我的理解是 Task.Wait() 将重新抛出异步操作引发的任何异常,并且我没有提供任何机制来取消异步操作。简单地调用 Task.Wait() 就足够了吗?
您链接到的博客中的示例代码仅使用异步同步来使用示例数据填充数据库;该调用不会存在于生产应用程序中。
首先,我想说,如果您确实需要
Configure
实现异步,那么您应该向 ASP.NET 团队提出问题,以便他们注意到这一问题。此时(即发布之前),他们添加对 ConfigureAsync
的支持并不会太困难。
其次,你有几种解决问题的方法。您可以使用
task.Wait
(或者更好的是,task.GetAwaiter().GetResult()
,如果确实发生错误,它可以避免使用AggregateException
包装器)。或者,您可以缓存任务的 task 而不是 result (如果 IMemoryCache
更像是一本字典而不是一些奇怪的序列化到内存中的二进制数组的东西,那么它就可以工作 - 我正在寻找向您发送 ASP.NET 的早期版本)。
如果这被认为是可接受的做法,那么我需要什么(如果有的话)错误处理?
使用
GetAwaiter().GetResult()
会导致异常(如果有)从 Configure
传播出去。不过,如果配置应用程序失败,我不确定 ASP.NET 将如何响应。
我没有提供任何取消异步操作的机制。
我不确定如何“取消”应用程序的设置,所以我不会担心这部分。
.NET Core 3.x 对此提供了更好的支持。
首先,您可以为缓存过程创建一个类。让它实现如下所示的
IHostedService
。只需要实现两个函数:
private readonly IServiceProvider _serviceProvider;
public SetupCacheService(IServiceProvider serviceProvider)
{
_serviceProvider = serviceProvider;
}
public async Task StartAsync(CancellationToken cancellationToken)
{
// Perform your caching logic here.
// In the below example I omit the caching details for clarity and
// instead show how to get a service using the service provider scope.
using (var scope = _serviceProvider.CreateScope())
{
// Example of getting a service you registered in the startup
var sampleService = scope.ServiceProvider.GetRequiredService<IYourService>();
// Perform the caching or database or whatever async work you need to do.
var results = sampleService.DoStuff();
var cacheEntryOptions = new MemoryCacheEntryOptions(){ // cache options };
// finish caching setup..
}
}
public Task StopAsync(CancellationToken cancellationToken) => Task.CompletedTask;
现在,在
Startup.cs
public virtual void ConfigureServices(IServiceCollection services)
{
// Normal service registration stuff.
// this is just an example. There are 1000x ways to do this.
services.AddTransient(IYourService, ConcreteService);
// Here you register the async work from above, which will
// then be executed before the app starts running
services.AddHostedService<SetupCacheService>();
}
就是这样。 请注意,我的解决方案很大程度上依赖于Andrew Lock 的文章。我非常感谢他花时间写下这些内容。
来自安德鲁·洛克我发布的链接,
服务将按照添加到 DI 容器中的顺序在启动时执行,即稍后在
中添加的服务将稍后在启动时执行。ConfigureServices
希望这对任何寻求 .NET Core 3.x+ 方法的人有所帮助。
您可以执行一些异步工作,但该方法是同步的,您无法更改它。这意味着您需要同步等待异步调用完成。
如果启动尚未完成,您不想从 Startup 方法返回,对吗?你的解决方案似乎没问题。
至于异常处理:如果有一项工作没有你的应用程序就无法正常运行,你应该让 Startup 方法失败(请参阅Fail-fast)。如果这不是关键问题,我会将相关部分包含在 try catch 块中,然后记录问题以供以后检查。
如果您的异步代码进行进一步的异步调用,特别是如果这些是回调,那么您可能会发现代码死锁,此处的答案并不总是正确工作。
Nito.AsyncEx
,效果很好。
using Nito.AsyncEx;
AsyncContext.Run(async () => { await myThing.DoAsyncTask(); });