如何在不调用 .Result 的情况下使用作为任务注入的类中的函数?
Posted
技术标签:
【中文标题】如何在不调用 .Result 的情况下使用作为任务注入的类中的函数?【英文标题】:How do I use functions within a class injected as a task without using calling .Result? 【发布时间】:2020-11-07 01:39:01 【问题描述】:我正在尝试在此处实施 lolsharp 给出的答案:gRPC-Web Channel Authentication with Blazor Webassembly via Dependency Injection
他们注入一个 GrpcChannel 类型的任务,其中 GrpcChannel 是一个类:
@inject Task<GrpcChannel> Channel
这是因为频道是使用异步注册的。
我尝试在此类中使用一个函数,如下所示:
GetAllResponse getAllResponse = await Channel.Result.GetAllAsync(new Google.Protobuf.WellKnownTypes.Empty());
但这会失败并出现错误“无法在此运行时等待监视器”,因为单线程 Web 程序集不支持该机制(如 Blazor Startup Error: System.Threading.SynchronizationLockException: Cannot wait on monitors on this runtime 中所述)。
如果通道是异步注册的,如何在 Razor 页面中使用 Channel 类中的函数?
编辑:根据要求,这是 Program.cs 中声明的依赖项
builder.Services.AddSingleton(async services =>
Console.WriteLine("In addsingleton");
var config = services.GetRequiredService<IConfiguration>();
#if DEBUG
var baseUri = "http://localhost:8999/";
#else
var baseUri = "[mysite]";
#endif
Console.WriteLine("About to set new httpclient");
var httpClient = new HttpClient(new
GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
var scopedFactory =
services.GetRequiredService<IServiceScopeFactory>();
using (var scope = scopedFactory.CreateScope())
var authenticationService =
scope.ServiceProvider.GetRequiredService<IAccessTokenProvider>();
var tokenResult = await
authenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
var credentials = CallCredentials.FromInterceptor((context, metadata) =>
if (!string.IsNullOrEmpty(token.Value))
metadata.Add("Authorization", $"Bearer
token.Value");
return Task.CompletedTask;
);
var channel = GrpcChannel.ForAddress(baseUri, new
GrpcChannelOptions HttpClient = httpClient, Credentials =
ChannelCredentials.Create(new SslCredentials(), credentials) );
var client = new
GrpcServices.GrpcServicesClient(channel);
return client;
【问题讨论】:
这能回答你的问题吗? gRPC-Web Channel Authentication with Blazor Webassembly via Dependency Injection 你能在你正在使用的 startup.cs 中发布你的依赖吗? @Hostel 不,这就是我的问题所在。第二个答案在解释如何使用该类之前就停止了。 @Dan 我已经添加了来自 Program.cs 的依赖项。 在对象解析期间防止做任何涉及 I/O 的事情。这使得分辨率变得脆弱且无法测试。相反,您应该可以compose your object graphs with con confidence。 【参考方案1】:为了避免在Channel
上调用.Result
,您必须等待它。例如:
var channel = await Channel;
GetAllResponse getAllResponse =
await channel.GetAllAsync(new Google.Protobuf.WellKnownTypes.Empty());
旁注:正如我在 cmets 中提到的,您通常应该避免在对象解析期间执行任何涉及 I/O 的操作,因为这会使对象解析变得脆弱且无法测试。相反,您应该能够自信地组合您的对象图,正如 Mark Seemann 所表达的here。您可以通过将任务隐藏在抽象后面来推迟任务的创建来做到这一点。例如:
public interface IGrpcChannelProvider
Task<GrpcChannel> Channel get;
这允许您将所有注册代码移动到IGrpcChannelProvider
的实现中:
public sealed class GrpcChannelProvider : IGrpcChannelProvider, IDisposable
private readonly IConfiguration config;
private readonly IAccessTokenProvider authenticationService;
private readonly Lazy<Task<GrpcChannel>> channel;
public GrpcChannelProvider(
IConfiguration config, IAccessTokenProvider authenticationService)
this.config = config;
this.authenticationService = authenticationService;
this.channel = new Lazy<Task<GrpcChannel>>(this.CreateChannel);
public Task<GrpcChannel> Channel => this.channel.Value;
public void Dispose()
if (this.channel.IsValueCreated) this.channel.Value.Dispose();
// This is your original code
private async Task<GrpcChannel> CreateChannel()
#if DEBUG
var baseUri = "http://localhost:8999/";
#else
var baseUri = "[mysite]";
#endif
var httpClient = new HttpClient(new GrpcWebHandler(GrpcWebMode.GrpcWeb, new HttpClientHandler()));
var tokenResult = await this.authenticationService.RequestAccessToken();
if (tokenResult.TryGetToken(out var token))
var credentials = CallCredentials.FromInterceptor((context, metadata) =>
if (!string.IsNullOrEmpty(token.Value))
metadata.Add("Authorization", $"Bearer token.Value");
return Task.CompletedTask;
);
var channel = GrpcChannel.ForAddress(baseUri,
new GrpcChannelOptions
HttpClient = httpClient,
Credentials = ChannelCredentials.Create(new SslCredentials(), credentials)
);
var client = new GrpcServices.GrpcServicesClient(channel);
return client;
这个组件可以注册如下:
services.AddSingleton<IGrpcChannelProvider, GrpcChannelProvider>();
或者 - 如果在应用程序域期间缓存通道导致安全问题 - 将组件注册为范围:
services.AddScoped<IGrpcChannelProvider, GrpcChannelProvider>();
在视图中,注入这个IGrpcChannelProvider
而不是通道:
@inject IGrpcChannelProvider Provider
并按如下方式使用:
var channel = await Provider.Channel;
GetAllResponse getAllResponse =
await channel.GetAllAsync(new Google.Protobuf.WellKnownTypes.Empty());
更进一步,您甚至可能希望阻止对 Razor 页面内的服务进行任何调用,而是依赖预填充模型:
@model AllResponseModel
@model AllResponseModel
GetAllResponse getAllResponse = Model.AllResponses;
现在您可以将 IGrpcChannelProvider
注入 Razor AllResponseModel
。
【讨论】:
谢谢。实际上,我一直在努力实施您之前发布的文章中的建议,但这些具体说明肯定会让这项任务变得更容易。以上是关于如何在不调用 .Result 的情况下使用作为任务注入的类中的函数?的主要内容,如果未能解决你的问题,请参考以下文章
Java 执行器:当任务完成时,如何在不阻塞的情况下得到通知?
如何在不调用渲染函数的情况下访问 Context 的值? [复制]
如何在不抛出 TaskCanceledExceptions 的情况下等待任务?