CreateContainerIfNotExistsAsync 挂起、停止、从不 returns
CreateContainerIfNotExistsAsync hangs, stalls, never returns
我有一个可以读取和写入 CosmosDb 的 Blazor 应用程序。尝试添加一些新功能,这些功能在本地运行良好,但在部署到 Azure 后就会出现问题。我已将问题追溯到 CosmosDbService
中的某处。我想将 ILogger
添加到 CosmosDbService
并进一步追踪我的问题。但是,在测试 DI 时,我的代码停滞在 CreateDatabaseIfNotExistsAsync(databasename)
。前面的代码没有错误。没有例外,没有超时,什么都没有,只是无声的失败。 Azure 未报告任何内容。如果我将所有内容回滚到原始状态,它就可以工作。显然它在某处,但我没有看到它。所以我的问题是 1) 为什么我的应用程序现在 stall/hang? 2) 我是否将 ILogger 正确地注入到我的 CosmosDbService 中?这是相关代码。
Startup.cs
....
services.AddSingleton<ICosmosDbService>((s) =>
{
var logger = s.GetRequiredService<ILogger<CosmosDbService>>();
return InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb"), logger).GetAwaiter().GetResult();
});
'''
private static async Task<CosmosDbService> InitializeCosmosClientInstanceAsync(IConfigurationSection configurationSection, ILogger logger)
{
string databaseName = configurationSection.GetSection("DatabaseName").Value;
string containerRatings = configurationSection.GetSection("ContainerRatings").Value;
string containerUsers = configurationSection.GetSection("ContainerUsers").Value;
string containerLocations = configurationSection.GetSection("ContainerLocations").Value;
string account = configurationSection.GetSection("Account").Value;
string key = configurationSection.GetSection("Key").Value;
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(account, key);
CosmosClient client = clientBuilder
.WithConnectionModeDirect()
.WithSerializerOptions(new CosmosSerializationOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default })
.Build();
//CosmosDbService cosmosDbService = new CosmosDbService(client, databaseName, containerRatings, containerUsers, containerLocations);
CosmosDbService cosmosDbService = new CosmosDbService()
{
ContainerRatings = client.GetContainer(databaseName, containerRatings),
ContainerRateLocationSmall = client.GetContainer(databaseName, containerLocations),
ContainerRateUser = client.GetContainer(databaseName, containerUsers),
CosmosClient = client,
DatabaseName = databaseName
, _logger = logger
};
DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerRatings, "/What");
await database.Database.CreateContainerIfNotExistsAsync(containerUsers, "/UserName");
await database.Database.CreateContainerIfNotExistsAsync(containerLocations, "/id");
return cosmosDbService;
}
...
CosmosDBService.cs
...
public class CosmosDbService : ICosmosDbService
{
public CosmosClient CosmosClient { get; set; }
public string DatabaseName { get; set; }
public Container ContainerRatings { get; set; }
public Container ContainerRateUser { get; set; }
public Container ContainerRateLocationSmall { get; set; }
public ILogger _logger { get; set; }
//public CosmosDbService() { }
...
提前致谢。
--更新 2020-12-06 ---
谢谢全哥。您的问题和建议激发了一种新的思路。我给你的答案。
- 我注意到
Startup.cs
中的新行导致服务在实际程序执行后延迟加载:
var logger = s.GetRequiredService<ILogger<CosmosDbService>>();
当我将此行返回到其原始语法时,它在启动期间成功初始化。
services.AddSingleton<ICosmosDbService>(InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
- 正在将 ILogger 注入我的 CosmosDBService。在日志记录 (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0) 上进行重新 RTFM 时,我似乎需要改用工厂。
所以我将实例化更改为:
LoggerFactory loggerFactory = new LoggerFactory();
//CosmosDbService cosmosDbService = new CosmosDbService(client, databaseName, containerR8ings, containerUsers, containerLocations);
CosmosDbService cosmosDbService = new CosmosDbService()
{
ContainerR8ings = dbClient.GetContainer(databaseName, containerR8ings),
ContainerR8User = dbClient.GetContainer(databaseName, containerUsers),
ContainerR8LocationSmall = dbClient.GetContainer(databaseName, containerLocations),
_logger = loggerFactory.CreateLogger("CosmosDbService")
};
并通过删除显式构造函数并使用隐式构造函数与 CosmosDBService 相对应。然后我就可以将 Logger 添加到服务中了。
- 最后,这使我能够跟踪我正在寻找的原始错误。
再次感谢您的帮助。
如您所见,您不能在服务注册过程中异步初始化服务。我在这些情况下使用的解决方案不是直接创建服务,而是 return 具有 async Task<T> CreateAsync
方法的工厂或提供者服务来执行此操作。您将此提供程序注册为服务,然后注入此提供程序并在代码中调用 CreateAsync
。这是一个例子:
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace TestWebsite.Services
{
public class CosmosDbProvider
{
/// <summary>
/// Ctor: values provided by DI
/// </summary>
/// <param name="configuration"></param>
/// <param name="logger"></param>
public CosmosDbProvider(IConfiguration configuration, ILogger logger)
{
config = configuration;
log = logger;
}
IConfiguration config;
ILogger log;
ICosmosDbService cachedService;
public async Task<ICosmosDbService> CreateAsync()
{
// use a cached instance for all requests (similar to Lazy<T> but async)
if (cachedService == null)
{
cachedService = await InitializeCosmosClientInstanceAsync();
}
return cachedService;
}
private async Task<CosmosDbService> InitializeCosmosClientInstanceAsync()
{
try
{
log.LogInformation("InitializeCosmosClientInstanceAsync started");
// I'd recommend GetValue<T> for reading single values over GetSection
string databaseName = config.GetValue<string>("DatabaseName");
string containerRatings = config.GetValue<string>("ContainerRatings");
string containerUsers = config.GetValue<string>("ContainerUsers");
string containerLocations = config.GetValue<string>("ContainerLocations");
string account = config.GetValue<string>("Account");
string key = config.GetValue<string>("Key");
log.LogDebug($"databaseName: {databaseName}");
log.LogDebug($"containerRatings: {containerRatings}");
log.LogDebug($"containerUsers: {containerUsers}");
log.LogDebug($"containerLocations: {containerLocations}");
// might not want to log sensitive info..
// log.LogDebug($"account: {account}");
// log.LogDebug($"key: {key}");
log.LogDebug("Creating CosmosClientBuilder");
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(account, key);
log.LogDebug("Creating CosmosClient");
CosmosClient client = clientBuilder
.WithConnectionModeDirect()
.WithSerializerOptions(new CosmosSerializationOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default })
.Build();
log.LogDebug("Creating CosmosDbService");
CosmosDbService cosmosDbService = new CosmosDbService()
{
ContainerRatings = client.GetContainer(databaseName, containerRatings),
ContainerRateLocationSmall = client.GetContainer(databaseName, containerLocations),
ContainerRateUser = client.GetContainer(databaseName, containerUsers),
CosmosClient = client,
DatabaseName = databaseName,
_logger = log
};
log.LogDebug($"CreateDatabase for '{databaseName}'");
DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
log.LogDebug($"CreateContainer for '{containerRatings}'");
await database.Database.CreateContainerIfNotExistsAsync(containerRatings, "/What");
log.LogDebug($"CreateContainer for '{containerUsers}'");
await database.Database.CreateContainerIfNotExistsAsync(containerUsers, "/UserName");
log.LogDebug($"CreateContainer for '{containerLocations}'");
await database.Database.CreateContainerIfNotExistsAsync(containerLocations, "/id");
log.LogInformation("InitializeCosmosClientInstanceAsync completed");
return cosmosDbService;
}
catch (Exception e)
{
log.LogError(e, "Failed when initializing CosmosDB");
throw;
}
}
}
}
我更改了您的配置代码以使用 GetValue<T>
- 我认为它更简单,并添加了一堆日志记录语句。大多数是 LogDebug
,因此在生产中您需要更改为调试级别(或者您可以根据需要更改为 LogInformation
)以帮助诊断问题。
在Startup.cs
中我们添加:
services.AddSingleton<CosmosDbProvider>();
要获取 CosmosDB 实例,您需要使用 DI 注入提供程序。在控制器、Razor 和 Blazor 页面中,您可以进行 async
调用:例如示例 Blazor 页面 Cosmos.razor
@page "/cosmos"
@inject TestWebsite.Services.CosmosDbProvider cosmosProvider;
<h3>Cosmos</h3>
@if(_db !=null)
{
<p>Your database is called @_db.DatabaseName</p>
}
@code {
TestWebsite.Services.ICosmosDbService _db;
protected override async Task OnInitializedAsync()
{
// create/get CosmosDbService
_db = await cosmosProvider.CreateAsync();
}
}
我有一个可以读取和写入 CosmosDb 的 Blazor 应用程序。尝试添加一些新功能,这些功能在本地运行良好,但在部署到 Azure 后就会出现问题。我已将问题追溯到 CosmosDbService
中的某处。我想将 ILogger
添加到 CosmosDbService
并进一步追踪我的问题。但是,在测试 DI 时,我的代码停滞在 CreateDatabaseIfNotExistsAsync(databasename)
。前面的代码没有错误。没有例外,没有超时,什么都没有,只是无声的失败。 Azure 未报告任何内容。如果我将所有内容回滚到原始状态,它就可以工作。显然它在某处,但我没有看到它。所以我的问题是 1) 为什么我的应用程序现在 stall/hang? 2) 我是否将 ILogger 正确地注入到我的 CosmosDbService 中?这是相关代码。
Startup.cs
....
services.AddSingleton<ICosmosDbService>((s) =>
{
var logger = s.GetRequiredService<ILogger<CosmosDbService>>();
return InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb"), logger).GetAwaiter().GetResult();
});
'''
private static async Task<CosmosDbService> InitializeCosmosClientInstanceAsync(IConfigurationSection configurationSection, ILogger logger)
{
string databaseName = configurationSection.GetSection("DatabaseName").Value;
string containerRatings = configurationSection.GetSection("ContainerRatings").Value;
string containerUsers = configurationSection.GetSection("ContainerUsers").Value;
string containerLocations = configurationSection.GetSection("ContainerLocations").Value;
string account = configurationSection.GetSection("Account").Value;
string key = configurationSection.GetSection("Key").Value;
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(account, key);
CosmosClient client = clientBuilder
.WithConnectionModeDirect()
.WithSerializerOptions(new CosmosSerializationOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default })
.Build();
//CosmosDbService cosmosDbService = new CosmosDbService(client, databaseName, containerRatings, containerUsers, containerLocations);
CosmosDbService cosmosDbService = new CosmosDbService()
{
ContainerRatings = client.GetContainer(databaseName, containerRatings),
ContainerRateLocationSmall = client.GetContainer(databaseName, containerLocations),
ContainerRateUser = client.GetContainer(databaseName, containerUsers),
CosmosClient = client,
DatabaseName = databaseName
, _logger = logger
};
DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
await database.Database.CreateContainerIfNotExistsAsync(containerRatings, "/What");
await database.Database.CreateContainerIfNotExistsAsync(containerUsers, "/UserName");
await database.Database.CreateContainerIfNotExistsAsync(containerLocations, "/id");
return cosmosDbService;
}
...
CosmosDBService.cs
...
public class CosmosDbService : ICosmosDbService
{
public CosmosClient CosmosClient { get; set; }
public string DatabaseName { get; set; }
public Container ContainerRatings { get; set; }
public Container ContainerRateUser { get; set; }
public Container ContainerRateLocationSmall { get; set; }
public ILogger _logger { get; set; }
//public CosmosDbService() { }
...
提前致谢。
--更新 2020-12-06 ---
谢谢全哥。您的问题和建议激发了一种新的思路。我给你的答案。
- 我注意到
Startup.cs
中的新行导致服务在实际程序执行后延迟加载:
var logger = s.GetRequiredService<ILogger<CosmosDbService>>();
当我将此行返回到其原始语法时,它在启动期间成功初始化。
services.AddSingleton<ICosmosDbService>(InitializeCosmosClientInstanceAsync(Configuration.GetSection("CosmosDb")).GetAwaiter().GetResult());
- 正在将 ILogger 注入我的 CosmosDBService。在日志记录 (https://docs.microsoft.com/en-us/aspnet/core/fundamentals/logging/?view=aspnetcore-5.0) 上进行重新 RTFM 时,我似乎需要改用工厂。 所以我将实例化更改为:
LoggerFactory loggerFactory = new LoggerFactory();
//CosmosDbService cosmosDbService = new CosmosDbService(client, databaseName, containerR8ings, containerUsers, containerLocations);
CosmosDbService cosmosDbService = new CosmosDbService()
{
ContainerR8ings = dbClient.GetContainer(databaseName, containerR8ings),
ContainerR8User = dbClient.GetContainer(databaseName, containerUsers),
ContainerR8LocationSmall = dbClient.GetContainer(databaseName, containerLocations),
_logger = loggerFactory.CreateLogger("CosmosDbService")
};
并通过删除显式构造函数并使用隐式构造函数与 CosmosDBService 相对应。然后我就可以将 Logger 添加到服务中了。
- 最后,这使我能够跟踪我正在寻找的原始错误。
再次感谢您的帮助。
如您所见,您不能在服务注册过程中异步初始化服务。我在这些情况下使用的解决方案不是直接创建服务,而是 return 具有 async Task<T> CreateAsync
方法的工厂或提供者服务来执行此操作。您将此提供程序注册为服务,然后注入此提供程序并在代码中调用 CreateAsync
。这是一个例子:
using Microsoft.Azure.Cosmos;
using Microsoft.Azure.Cosmos.Fluent;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.Logging;
using System;
using System.Threading.Tasks;
namespace TestWebsite.Services
{
public class CosmosDbProvider
{
/// <summary>
/// Ctor: values provided by DI
/// </summary>
/// <param name="configuration"></param>
/// <param name="logger"></param>
public CosmosDbProvider(IConfiguration configuration, ILogger logger)
{
config = configuration;
log = logger;
}
IConfiguration config;
ILogger log;
ICosmosDbService cachedService;
public async Task<ICosmosDbService> CreateAsync()
{
// use a cached instance for all requests (similar to Lazy<T> but async)
if (cachedService == null)
{
cachedService = await InitializeCosmosClientInstanceAsync();
}
return cachedService;
}
private async Task<CosmosDbService> InitializeCosmosClientInstanceAsync()
{
try
{
log.LogInformation("InitializeCosmosClientInstanceAsync started");
// I'd recommend GetValue<T> for reading single values over GetSection
string databaseName = config.GetValue<string>("DatabaseName");
string containerRatings = config.GetValue<string>("ContainerRatings");
string containerUsers = config.GetValue<string>("ContainerUsers");
string containerLocations = config.GetValue<string>("ContainerLocations");
string account = config.GetValue<string>("Account");
string key = config.GetValue<string>("Key");
log.LogDebug($"databaseName: {databaseName}");
log.LogDebug($"containerRatings: {containerRatings}");
log.LogDebug($"containerUsers: {containerUsers}");
log.LogDebug($"containerLocations: {containerLocations}");
// might not want to log sensitive info..
// log.LogDebug($"account: {account}");
// log.LogDebug($"key: {key}");
log.LogDebug("Creating CosmosClientBuilder");
CosmosClientBuilder clientBuilder = new CosmosClientBuilder(account, key);
log.LogDebug("Creating CosmosClient");
CosmosClient client = clientBuilder
.WithConnectionModeDirect()
.WithSerializerOptions(new CosmosSerializationOptions { PropertyNamingPolicy = CosmosPropertyNamingPolicy.Default })
.Build();
log.LogDebug("Creating CosmosDbService");
CosmosDbService cosmosDbService = new CosmosDbService()
{
ContainerRatings = client.GetContainer(databaseName, containerRatings),
ContainerRateLocationSmall = client.GetContainer(databaseName, containerLocations),
ContainerRateUser = client.GetContainer(databaseName, containerUsers),
CosmosClient = client,
DatabaseName = databaseName,
_logger = log
};
log.LogDebug($"CreateDatabase for '{databaseName}'");
DatabaseResponse database = await client.CreateDatabaseIfNotExistsAsync(databaseName);
log.LogDebug($"CreateContainer for '{containerRatings}'");
await database.Database.CreateContainerIfNotExistsAsync(containerRatings, "/What");
log.LogDebug($"CreateContainer for '{containerUsers}'");
await database.Database.CreateContainerIfNotExistsAsync(containerUsers, "/UserName");
log.LogDebug($"CreateContainer for '{containerLocations}'");
await database.Database.CreateContainerIfNotExistsAsync(containerLocations, "/id");
log.LogInformation("InitializeCosmosClientInstanceAsync completed");
return cosmosDbService;
}
catch (Exception e)
{
log.LogError(e, "Failed when initializing CosmosDB");
throw;
}
}
}
}
我更改了您的配置代码以使用 GetValue<T>
- 我认为它更简单,并添加了一堆日志记录语句。大多数是 LogDebug
,因此在生产中您需要更改为调试级别(或者您可以根据需要更改为 LogInformation
)以帮助诊断问题。
在Startup.cs
中我们添加:
services.AddSingleton<CosmosDbProvider>();
要获取 CosmosDB 实例,您需要使用 DI 注入提供程序。在控制器、Razor 和 Blazor 页面中,您可以进行 async
调用:例如示例 Blazor 页面 Cosmos.razor
@page "/cosmos"
@inject TestWebsite.Services.CosmosDbProvider cosmosProvider;
<h3>Cosmos</h3>
@if(_db !=null)
{
<p>Your database is called @_db.DatabaseName</p>
}
@code {
TestWebsite.Services.ICosmosDbService _db;
protected override async Task OnInitializedAsync()
{
// create/get CosmosDbService
_db = await cosmosProvider.CreateAsync();
}
}