【问题标题】:Correct pattern to call a service containing an async call from an MVC controller从 MVC 控制器调用包含异步调用的服务的正确模式
【发布时间】:2016-05-24 03:19:49
【问题描述】:

我是 TAP 的新手,并且在 C# 的实践中使用 async/await,所以我可能在这里有一些不好的代码味道,所以要温柔。 :-)

我有一个如下所示的服务方法:

public OzCpAddOrUpdateEmailAddressToListOutput AddOrUpdateEmailAddressToList(
    OzCpAddOrUpdateEmailAddressToListInput aParams)
{
    var result = new OzCpAddOrUpdateEmailAddressToListOutput();
    try
    {
        var mailChimManager = new MailChimpManager(aParams.MailChimpApiKey);
        Task<Member> mailChimpResult =
            mailChimManager.Members.AddOrUpdateAsync(
                aParams.Listid, 
                new Member                                                                                                     
                {
                    EmailAddress = aParams.EmailAddress
                });

        //Poll async task until it completes. 
        //Give it at most 8 seconds to do what it needs to do
        var outOfTime = DateTime.Now.AddSeconds(8);
        while (!mailChimpResult.IsCompleted)
        {
            if (DateTime.Now > outOfTime)
            {
                throw new Exception("Timed out waiting for MailChimp API.");
            }
        }

        //Should there have been a problem with the call then we raise an exception
        if (mailChimpResult.IsFaulted)
        {
            throw new Exception(
                mailChimpResult.Exception?.Message ?? 
                "Unknown mail chimp library error.", 
                mailChimpResult.Exception);
        }
        else
        {
            //Call to api returned without failing but unless we have 
            //the email address subscribed we have an issue
            if (mailChimpResult.Result.Status != Status.Subscribed)
            {
                throw new Exception(
                    $"There was a problem subscribing the email address
                    {aParams.EmailAddress} to the mailchimp list id 
                    {aParams.Listid}");
            }
        }
    }
    catch (Exception ex)
    {
        result.ResultErrors.AddFatalError(PlatformErrors.UNKNOWN, ex.Message);
    }
    return result;
}

但是当我从 MVC 控制器 调用时,动作 mailChimpResult.IsCompleted 总是返回 false,最终我遇到了超时。

我意识到这是因为我没有按照 HttpClient IsComplete always return false 链接异步调用,并且由于线程不同,这种行为是“预期的”。

但是,我希望我的服务方法隐藏它正在做的事情的 async 性质的复杂性,并且只在我的操作方法中执行看似同步调用的操作:

 var mailChimpResult =
    _PlatformMailChimpService.AddOrUpdateEmailAddressToList(
        new OzCpAddOrUpdateEmailAddressToListInput                                                                                                                                            
        {
            EmailAddress = aFormCollection["aEmailAddress"],                                                                                                                                           
            Listid = ApplicationSettings.Newsletter.MailChimpListId.Value,                                                                                                                                          
            MailChimpApiKey = ApplicationSettings.Newsletter.MailChimpApiKey.Value
        });

 if (mailChimpResult.Result == true)
 {
    //So something
 }

【问题讨论】:

  • 你需要一直做async。 “隐藏”它总是会导致痛苦的发现——通常是deadlock。基本上没有办法解决它。
  • @AlexeiLevenkov 但由于我的服务调用没有返回异步句柄等,我应该如何实现这一点。你能根据我的代码给我一个例子吗?
  • 您在服务调用中隐藏了它。你需要await 调用AddOrUpdateAsync,而不是轮询它——这是大代码的味道。然后你可以使服务方法异步并在控制器中等待。

标签: c# asp.net-mvc asynchronous async-await


【解决方案1】:

理想情况下,您应该避免 TaskTask&lt;T&gt; 对象的 .Result.IsFaulted 属性,这是代码气味的第一名。当您使用这些对象时,您应该在整个堆栈中使用asyncawait。考虑以这种方式编写您的服务:

public async Task<OzCpAddOrUpdateEmailAddressToListOutput> 
    AddOrUpdateEmailAddressToList(
        OzCpAddOrUpdateEmailAddressToListInput aParams)
{
    var result = new OzCpAddOrUpdateEmailAddressToListOutput();
    try
    {
        var mailChimManager = new MailChimpManager(aParams.MailChimpApiKey);
        Member mailChimpResult =
            await mailChimManager.Members.AddOrUpdateAsync(
                aParams.Listid, 
                new Member                                                                                                     
                {
                    EmailAddress = aParams.EmailAddress
                });
    }
    catch (Exception ex)
    {
        result.ResultErrors.AddFatalError(PlatformErrors.UNKNOWN, ex.Message);
    }
    return result;
}

请注意,我能够删除所有不必要的轮询和属性检查。我们将方法标记为Task&lt;OzCpAddOrUpdateEmailAddressToListOutput&gt; 返回并用async 关键字装饰它。这允许我们在方法体中使用await 关键字。我们await.AddOrUpdateAsync 产生Member

对服务的消费调用看起来很相似,遵循 asyncawait 关键字的相同范例,带有 TaskTask&lt;T&gt; 返回类型:

var mailChimpResult =
    await _PlatformMailChimpService.AddOrUpdateEmailAddressToList(
        new OzCpAddOrUpdateEmailAddressToListInput                                                                                                                                            
        {
            EmailAddress = aFormCollection["aEmailAddress"],                                                                                                                                           
            Listid = ApplicationSettings.Newsletter.MailChimpListId.Value,                                                                                                                                          
            MailChimpApiKey = ApplicationSettings.Newsletter.MailChimpApiKey.Value
        });

 if (mailChimpResult.Result == true)
 {
    //So something
 }

最好的做法是在方法后面加上“Async”这个词,表示它是异步的,即; AddOrUpdateEmailAddressToListAsync.

【讨论】:

  • 然后控制器方法就变成了var mailChimpResult = await _PlatformMailChimpService.AddOrUpdateEmailAddressToList(...); if (mailChimpResult.Result == true){},所以除了添加一个词之外,仍然隐藏了复杂性
  • @David 谢谢。在看到你的回复之前我已经重构了。唯一的进一步补充是使控制器动作也异步。
猜你喜欢
  • 1970-01-01
  • 2014-07-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-09
  • 1970-01-01
相关资源
最近更新 更多