如何在Startup.Configure中处理异步操作?

时间:2015-08-27 19:10:01

标签: c# asp.net-core async-await

在我的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()就足够了吗?

4 个答案:

答案 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(); });