【问题标题】:Why does async/await method in attribute never return为什么属性中的异步/等待方法永远不会返回
【发布时间】:2015-09-21 20:18:01
【问题描述】:

好吧,请耐心等待,这可能需要一些解释,因此我有一个简单的帐户控制器;

[RoutePrefix("api/account")]
[Authorize]
[HmacAuthentication]
public class AccountController : ApiController
{
    public async Task<IHttpActionResult> Register(UserModel userModel)
    {
        if (!this.ModelState.IsValid)
        {
            return this.BadRequest(this.ModelState);
        }

        IdentityResult result = await this._userService.RegisterUser(userModel);

        var errorResult = this.GetErrorResult(result);
        if (errorResult != null)
        {
            return errorResult;
        }

        return this.Ok();
    }
}

HmacAuthentication 属性在这里:

public class HmacAuthenticationAttribute : Attribute, IAuthenticationFilter
{
    public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        ...

        var isValid = this.IsValidRequest(req, appId, incomingBase64Signature, nonce, requestTimeStamp);

        if (isValid.Result)
        {
            ...
        }
        ...
    }

    private async Task<bool> IsValidRequest(
        HttpRequestMessage req, 
        string appId, 
        string incomingBase64Signature, 
        string nonce, 
        string requestTimeStamp)
    {
        ...
        var user = await this.UserService.FindUser(userId); // this never gets a return value
        ...
    }
}

UserService中调用的方法是这样的:

public async Task<ApplicationUserModel> FindUser(int id)
{
    var user = await this._userBusiness.FindAsync(id);
    return this.MapToModel(user);
}

商务舱是这样的:

public async Task<ApplicationUser> FindAsync(int id)
{
    var result = await this._userManager.FindByIdAsync(id);
    return result;
}

我面临的问题是,当调用 Register 方法时,HmacAuthentication 属性会触发并且 AuthenticateAsync 过滤器方法会执行。 IsValidRequest 中查找用户的调用实际上永远不会获得返回值,如果我通过邮递员尝试请求它永远不会完成。

有人可以帮忙吗?

【问题讨论】:

  • 你用isValid做什么?你在任何地方都写过await吗?
  • 你能判断代码是否卡在isValid.Result这一行吗?如果是这样,您可能遇到了死锁。阅读here,了解为什么您通常不想使用Task.Result 来等待任务。
  • 有时 var 是一件坏事,它会让你忘记等待...
  • @sstan 谢谢我只是假设是上面的行,因为调试器从来没有那么远,链接以及下面的答案都有帮助

标签: c# .net asp.net-web-api async-await task-parallel-library


【解决方案1】:
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
    {
        ...

        var isValid = await this.IsValidRequest(req, appId, incomingBase64Signature, nonce, requestTimeStamp);

        if (isValid)
        {
            ...
        }
        ...
    }

按照编译器的建议,您只能在标记为async 的方法中使用await 关键字。所以我相应地更新了 AuthenticateAsync 的签名。另请注意,您现在可以只检查isValid 而不是isValid.Result,因为布尔值将在等待行之后可用。

这可能有点令人困惑,因为 IAuthenticationFilter 的接口没有指定异步(接口不能,它们只能指示该方法将返回一个任务)。由实现者决定该方法是简单地返回任务还是提供异步以便可以在方法主体内等待值。

【讨论】:

  • 我试过了,但我得到一个编译时错误The 'await' operator can only be used in a method or lambda marked with the 'async' modifier我调用的方法是在框架接口上public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
  • 我没有意识到你可以用 async 来装饰方法,这不是你的方法sstan 它确实在isValid.Result 上被阻止了,假设它是前面的线路,因为我在穿过时从未走到那一步
  • 您只能在返回任务的接口方法上执行此操作。 async/await 肯定需要花点时间来理解它,但它非常强大,一旦你进入它,你会发现自己想知道没有它你是如何编码的 :)
【解决方案2】:

接受的答案在如何解决您的问题方面是正确的,但没有解释为什么会发生这种情况。

这个:

var isValid = this.IsValidRequest(
                  req, 
                  appId, 
                  incomingBase64Signature, 
                  nonce, 
                  requestTimeStamp);

if (isValid.Result)

导致您的代码死锁。您正在同步阻塞异步方法。发生这种情况是因为当编译器看到 async 方法时,它会生成一个状态机,该状态机会获取您的第一个 await 之后的所有内容并将其包装为延续。编译器还会查看是否涉及任何同步上下文,如果有,它会尝试将延续封送回 AspNetSynchronizationContext,这会被您的 .Result 调用阻止,从而有效地导致死锁。

这就是为什么you shouldn't block on async code

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2019-01-16
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-09-01
    • 1970-01-01
    • 2017-03-21
    • 2018-04-04
    相关资源
    最近更新 更多