在我的 ASP.NET 5 应用程序中,我想将一些数据从 Azure 加载到我的 Startup.Configure 方法中的缓存中。 Azure SDK 专门公开异步方法。通常,调用异步方法是通过异步方法中的 await 完成的,如下所示:
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 以及许多其他方法)。
我在想更好的方法是在没有等待的情况下调用异步函数,在返回的任务上调用等待,然后通过 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() 就足够了吗?
async void
确实在这种情况下会产生副作用,因为它会向调用该方法的任何人发出信号,表明他们可以在该方法运行时继续进行其他工作。由于 Configure
处理应用程序设置,因此在完成之前继续运行应用程序代码的其他部分可能会产生不可预知的后果。 (我们遇到了这个问题,它使我们的应用程序变得混乱,因为依赖注入器注入了尚未正确设置的服务)。
您链接到的博客中的示例代码仅使用异步同步来使用示例数据填充数据库;该调用不会存在于生产应用程序中。
首先,我想说,如果您真的需要 Configure
是异步的,那么您应该向 ASP.NET 团队提出一个问题,以便他们关注。此时(即在发布之前)添加对 ConfigureAsync
的支持对他们来说并不难。
其次,您有几种解决问题的方法。您可以使用 task.Wait
(或者更好的是 task.GetAwaiter().GetResult()
,这样可以在发生错误时避免使用 AggregateException
包装器)。或者,您可以缓存 task 而不是任务的 result (如果 IMemoryCache
更像是一个字典而不是一些奇怪的 serialize-into-binary-array -内存中的东西 - 我在看着你,以前版本的 ASP.NET)。
如果这被认为是可接受的做法,我需要什么(如果有的话)错误处理?
使用 GetAwaiter().GetResult()
会导致异常(如果有)传播到 Configure
之外。不过,如果 configuring 应用程序失败,我不确定 ASP.NET 会如何响应。
我没有提供任何机制来取消异步操作。
我不确定如何“取消”应用程序的设置,所以我不会担心它的那部分。
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+ 方法的人。
Writable
设置。虽然我不知道您的解决方案的完整规格,但我想您在 HostedService 中注入 IServiceProvider
并让您的服务访问数据库。您也可以注入 IWritableOptions<T>
并更新选项。然后那些 IWritableOptions
在整个应用程序中都可用。我还没有完全尝试过,但我已经分别完成了这两个步骤。
你可以做一些异步工作,但方法是同步的,你不能改变它。这意味着您需要同步等待异步调用完成。
如果启动尚未完成,您不想从 Startup 方法返回,对吗?您的解决方案似乎没问题。
至于异常处理:如果没有某项工作,您的应用程序将无法正常运行,您应该让 Startup 方法失败(参见Fail-fast)。如果不是很重要,我会将相关部分包含在 try catch 块中,然后将问题记录下来以供以后检查。
如果您的异步代码进行进一步的异步调用,特别是如果这些是回调,那么这里的答案并不总是正确的,那么您可能会发现代码死锁。
这对我来说发生过很多次,并且使用 Nito.AsyncEx
效果很好。
using Nito.AsyncEx;
AsyncContext.Run(async () => { await myThing.DoAsyncTask(); });
不定期副业成功案例分享
GetAwaiter()
是不够的。您必须使用GetAwaiter().GetResult()
才能“完成”异步操作。