在我的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。我可以使用异步void方法,但我的理解是async void方法只应该用于事件处理程序(根据https://msdn.microsoft.com/en-us/magazine/jj991977.aspx和其他许多方法)。
我认为更好的方法是在没有等待的情况下调用异步函数,在返回的Task上调用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今年早些时候在blog post采用了类似的方法。但是,如果这被认为是可以接受的做法,则从该帖子中不清楚。是吗?
如果这被认为是可接受的做法,我需要什么 - 如果有的话 - 错误处理?我的理解是Task.Wait()将重新抛出异步操作引发的任何异常,并且我还没有提供任何取消异步操作的机制。简单地调用Task.Wait()就足够了吗?
答案 0 :(得分:30)
您链接到的博客中的示例代码仅使用sync-over-async来使用示例数据填充数据库;该电话不会存在于制作应用中。
首先,我要说如果你真的需要Configure
是异步的,那么你应该向ASP.NET团队提出一个问题,以便他们在他们的雷达上。他们在此时(即发布前)添加对ConfigureAsync
的支持并不太难。
其次,您已经有了几种解决问题的方法。您可以使用task.Wait
(或者更好,task.GetAwaiter().GetResult()
,如果发生错误,则会避免使用AggregateException
包装器。或者,您可以缓存任务而不是任务的结果(如果IMemoryCache
更多是字典而不是某些奇怪的序列化到二进制,那么它可以正常工作 - 内存中的事物 - 我正在看着你,以前版本的ASP.NET。)
如果这被认为是可接受的做法,我需要什么 - 如果有的话 - 错误处理?
使用GetAwaiter().GetResult()
会导致异常(如果有)传播出Configure
。但是,如果配置应用程序失败,我不确定ASP.NET会如何响应。
我还没有提供取消异步操作的任何机制。
我不知道你怎么能取消&#34;取消&#34;设置应用程序,所以我不会担心它的那一部分。
答案 1 :(得分:2)
Dotnet 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;
现在,在Starup.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's article。我非常感谢他抽出宝贵的时间来写这篇文章。
在我由安德鲁·洛克(Andrew Lock)发布的链接中,
服务将在启动时按照添加到DI容器中的顺序执行,即稍后在ConfigureServices中添加的服务将在启动时执行。
希望这对寻找Dotnet core 3.x +方法的人有所帮助。
答案 2 :(得分:1)
您可以执行一些异步工作,但该方法是同步的,您无法更改它。这意味着您需要同步等待异步调用完成。
如果启动尚未完成,您不想从启动方法返回,对吗?你的解决方案似乎没问题。
至于异常处理:如果你的应用程序无法正常运行,你应该让Startup方法失败(参见Fail-fast)。如果它不重要,我会将相关部分包含在try catch块中,并记录问题以供日后检查。
答案 3 :(得分:0)
如果您的异步代码进行了进一步的异步调用(尤其是那些回调),那么此处的答案并不总是能正常工作,那么您可能会发现代码死锁。
对我来说,这已经发生过很多次了,并且使用Nito.AsyncEx
的效果很好。
using Nito.AsyncEx;
AsyncContext.Run(async () => { await myThing.DoAsyncTask(); });