如何在Blazor中从同步方法调用异步方法并等待结果?

4
在Blazor WebAssembly中,我有一个JavaScript函数在js文件中:
function AksMessage(message) {
    return confirm(message);
}

在 Razor 文件中:

[Inject]
public IJSRuntime JSRuntime { get; set; }
    
public async Task<bool> askMessage(msg)
{
    await JSRuntime.InvokeVoidAsync("AskMessage", msg);
}

现在我想在一些非异步函数中调用askMessage,并在用户点击并返回false或true时获取结果。我该如何运行它并等待同步代码的结果呢?如果我这样做:

var askmsg = Task.Run(async () => await askMessage("question"));

当使用askmsg.Result时,我遇到了一个异常,表示监视器无法在此运行时等待。


1
你的非异步代码在哪里以及为什么需要它?这似乎是不必要的。请提供更多细节以便回答该问题。 - H H
AksMessage 不等于 AskMessage - aepot
3
“Async All The Way(异步从头到尾)”不仅仅是一句口号,而几乎是一条规则。任何从异步切换回同步的操作都可能导致死锁。如果您需要了解更多背景知识,可以阅读 Stephen Cleary 多年来关于 DotNet 异步编码的文章。是什么事件/进程迫使您切换回同步模式? - MrC aka Shaun Curtis
我怀疑senterd正在尝试调用一个同步的Javascript函数。 - Andrew Rondeau
我的使用情况是我正在使用gRPC客户端拦截器。我无法使我想要覆盖的方法异步化,而我必须在其中执行异步操作(从身份服务器获取令牌)。 - Julien Debache
2个回答

1

我刚刚解决了在gRPC拦截器中设置承载令牌所需的一些WASM异步代码,您可以像这样完成:

public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
    TRequest request,
    ClientInterceptorContext<TRequest, TResponse> context,
    AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
{
    var call = continuation(request, context);

    return new AsyncUnaryCall<TResponse>(
        call.ResponseAsync,
        GetMetadata(),
        call.GetStatus,
        call.GetTrailers,
        call.Dispose);
}

private async Task<Metadata> GetMetadata()
{
    try
    {
        if (!string.IsNullOrEmpty(await _authenticationManager.TryRefreshToken()))
            _snackbar.Add("Refreshed Token.", Severity.Success);
        var token = _authenticationManager.Token;
        var metadata = new Metadata();
        if (!string.IsNullOrEmpty(token))
        {
            metadata.Add("Authorization", $"Bearer {token}");
            var userIdentity = (await _authenticationStateProvider.GetAuthenticationStateAsync()).User.Identity;
            if (userIdentity!.IsAuthenticated)
                metadata.Add("User", userIdentity.Name!);
        }
        else
        {
            _authenticationManager.Logout().GetAwaiter().GetResult();
            _navigationManager.NavigateTo("/");
        }

        return metadata;
    }
    catch (Exception ex)
    {
        throw new InvalidOperationException("Failed to add token to request metadata", ex);
    }
}

编辑:由于某些原因,尽管此异步代码运行了,但它实际上并没有将标头设置到请求中,所以您必须执行以下黑客操作:
public class AuthenticationInterceptor : Interceptor
{
    private readonly IAuthenticationManager _authenticationManager;
    private readonly ISnackbar _snackbar;
    private readonly NavigationManager _navigationManager;
    private readonly AuthenticationStateProvider _authenticationStateProvider;
    private Metadata? _metadata;

    public AuthenticationInterceptor(
        IAuthenticationManager authenticationManager,
        ISnackbar snackbar,
        NavigationManager navigationManager,
        AuthenticationStateProvider authenticationStateProvider)
    {
        _authenticationManager = authenticationManager;
        _snackbar = snackbar;
        _navigationManager = navigationManager;
        _authenticationStateProvider = authenticationStateProvider;
    }

    public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(
        TRequest request,
        ClientInterceptorContext<TRequest, TResponse> context,
        AsyncUnaryCallContinuation<TRequest, TResponse> continuation)
    {
        _ = new AsyncUnaryCall<TResponse>(
            null!,
            GetMetadata(context),
            null!,
            null!,
            null!); //  Doesn't actually send the request but runs the Task
                    // that provides the metadata to the field which can
                    // then be used to set the request headers
        
        var newOptions = context.Options.WithHeaders(_metadata!);

        var newContext = new ClientInterceptorContext<TRequest, TResponse>(
            context.Method,
            context.Host,
            newOptions);
 
        return base.AsyncUnaryCall(request, newContext, continuation);
    }

    private async Task<Metadata> GetMetadata<TRequest, TResponse>(ClientInterceptorContext<TRequest, TResponse> context)
        where TRequest : class
        where TResponse : class
    {
        try
        {
            if (!string.IsNullOrEmpty(await _authenticationManager.TryRefreshToken()))
                _snackbar.Add("Refreshed Token.", Severity.Success);
            var token = _authenticationManager.Token;
            Console.WriteLine($"Token: {token}");
            var headers = new Metadata();
            if (!string.IsNullOrEmpty(token))
            {
                headers.Add(new Metadata.Entry("Authorization", $"Bearer {token}"));
                Console.WriteLine("Set metadata");
                var userIdentity = (await _authenticationStateProvider.GetAuthenticationStateAsync()).User.Identity;
                if (userIdentity!.IsAuthenticated)
                    headers.Add(new Metadata.Entry("User", userIdentity.Name!));
            }
            else
            {
                await _authenticationManager.Logout();
                _navigationManager.NavigateTo("/");
            }
            
            var callOptions = context.Options.WithHeaders(headers);
                
            return _metadata = callOptions.Headers!;
        }
        catch (Exception ex)
        {
            throw new InvalidOperationException("Failed to add token to request headers", ex);
        }
    }
}

-3
您可以从同步方法中调用异步方法,并像这样等待它:
var askmsg = Task.Run(async () => await askMessage("question"));
var result = Task.WaitAndUnwrapException();

另一种解决方案是这样的(同步方法返回到其上下文):

var result = AsyncContext.RunTask(askMessage("question")).Result;

1
不起作用。问题涉及在Blazor内运行,其中线程模型与普通的.Net显着不同。Task.WaitAndUnwrapException();和AsyncContext.RunTask()不是有效的C#代码。与普通的.Net不同,Blazor(在WebAssembly内运行)是单线程的。如果您尝试阻塞线程以等待任务完成,将会出现运行时异常。 - Andrew Rondeau
同意,这个解决方案不起作用。像.Result.GetAwaiter().GetResult()这样的操作都会抛出异常。 - Julien Debache

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接