如何在不调用 .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 的情况下等待任务?

如何在不通过 SSL 添加服务引用的情况下动态调用 Web 服务

如何在不打开新命令行窗口的情况下使用“schtasks”执行计划任务?