来自异步方法的 Mvc .Net 捕获异常

Posted

技术标签:

【中文标题】来自异步方法的 Mvc .Net 捕获异常【英文标题】:Mvc .Net Catch Exception from async method 【发布时间】:2016-12-17 23:37:31 【问题描述】:

我正在使用 this 库作为 Mailchimp API v3.0 的包装器

现在我无法从这个库的方法中捕获异常。

这些方法是从Exception 扩展而来的MailChimpException

问题是,如果我从控制台应用程序运行它,那么我可以捕获异常,但是当我从我的 Web 项目运行它时,我无法捕获异常,并且代码在发生异常时卡住了。

这是我的电话示例:

public bool ListSubscribe(string emailAddress, string apiKey, string listId)

    try
    
       var api = new MailChimpManager(apiKey);
       var profile = new Member();
       profile.EmailAddress = emailAddress;
       var output = api.Members.AddOrUpdateAsync(listId, profile).Result;
       return true;
    
    catch (Exception ex)
    
       logger.Error("An error ocurred in trying to Subscribe to Mailchimp list. 0", ex.Message);
       return false;
    

这是我调用的库中的方法:

namespace MailChimp.Net.Logic

    /// <summary>
    /// The member logic.
    /// </summary>
    internal class MemberLogic : BaseLogic, IMemberLogic
    
        private const string BaseUrl = "lists";

        /// <summary>
        /// Initializes a new instance of the <see cref="MemberLogic"/> class.
        /// </summary>
        /// <param name="apiKey">
        /// The api key.
        /// </param>
        public MemberLogic(string apiKey)
            : base(apiKey)
        
        

        /// <summary>
        /// The add or update async.
        /// </summary>
        /// <param name="listId">
        /// The list id.
        /// </param>
        /// <param name="member">
        /// The member.
        /// </param>
        /// <returns>
        /// The <see cref="Task"/>.
        /// </returns>
        /// <exception cref="ArgumentNullException"><paramref>
        ///         <name>uriString</name>
        ///     </paramref>
        ///     is null. </exception>
        /// <exception cref="UriFormatException">In the .NET for Windows Store apps or the Portable Class Library, catch the base class exception, <see cref="T:System.FormatException" />, instead.<paramref name="uriString" /> is empty.-or- The scheme specified in <paramref name="uriString" /> is not correctly formed. See <see cref="M:System.Uri.CheckSchemeName(System.String)" />.-or- <paramref name="uriString" /> contains too many slashes.-or- The password specified in <paramref name="uriString" /> is not valid.-or- The host name specified in <paramref name="uriString" /> is not valid.-or- The file name specified in <paramref name="uriString" /> is not valid. -or- The user name specified in <paramref name="uriString" /> is not valid.-or- The host or authority name specified in <paramref name="uriString" /> cannot be terminated by backslashes.-or- The port number specified in <paramref name="uriString" /> is not valid or cannot be parsed.-or- The length of <paramref name="uriString" /> exceeds 65519 characters.-or- The length of the scheme specified in <paramref name="uriString" /> exceeds 1023 characters.-or- There is an invalid character sequence in <paramref name="uriString" />.-or- The MS-DOS path specified in <paramref name="uriString" /> must start with c:\\.</exception>
        /// <exception cref="MailChimpException">
        /// Custom Mail Chimp Exception
        /// </exception>
        /// <exception cref="TargetInvocationException">The algorithm was used with Federal Information Processing Standards (FIPS) mode enabled, but is not FIPS compatible.</exception>
        /// <exception cref="ObjectDisposedException">
        /// The object has already been disposed.
        /// </exception>
        /// <exception cref="EncoderFallbackException">
        /// A fallback occurred (see Character Encoding in the .NET Framework for complete explanation)-and-<see cref="P:System.Text.Encoding.EncoderFallback"/> is set to <see cref="T:System.Text.EncoderExceptionFallback"/>.
        /// </exception>
        /// <exception cref="ArgumentOutOfRangeException">
        /// Enlarging the value of this instance would exceed <see cref="P:System.Text.StringBuilder.MaxCapacity"/>. 
        /// </exception>
        /// <exception cref="FormatException">
        /// <paramref>
        ///         <name>format</name>
        ///     </paramref>
        /// includes an unsupported specifier. Supported format specifiers are listed in the Remarks section.
        /// </exception>
        public async Task<Member> AddOrUpdateAsync(string listId, Member member)
        
            using (var client = this.CreateMailClient($"BaseUrl/"))
            
                var response =
                await
                client.PutAsJsonAsync($"listId/members/this.Hash(member.EmailAddress.ToLower())", member, null).ConfigureAwait(false);

                await response.EnsureSuccessMailChimpAsync().ConfigureAwait(false);

                return await response.Content.ReadAsAsync<Member>().ConfigureAwait(false);
            
        
    

有谁知道为什么我能够在控制台应用程序中触发异常,但在 Web 项目中却不能。有什么建议检查什么?

更新: 如果我将代码更改为使用Task.Run,例如:

var task = Task.Run(async () =>  await api.Members.AddOrUpdateAsync(listId, profile); );
                    task.Wait();

所以:

public bool ListSubscribe(string emailAddress, string apiKey, string listId)

    try
    
       var api = new MailChimpManager(apiKey);
       var profile = new Member();
       profile.EmailAddress = emailAddress;
       var task = Task.Run(async () =>  await api.Members.AddOrUpdateAsync(listId, profile); );
                    task.Wait();
       return true;
    
    catch (Exception ex)
    
       logger.Error("An error ocurred in trying to Subscribe to Mailchimp list. 0", ex.Message);
       return false;
    

然后我在 catch 块中捕获异常,但是当一切正常时我如何从任务中获取结果???

【问题讨论】:

既然AddOrUpdateAsync()被标记为async,你不需要用await来调用它吗?例如var output = await api.Members.AddOrUpdateAsync(listId, profile); @JasonEvans 但是我必须将我的方法设置为异步,我不希望这样。我需要结果并且使用 .Result 它正在工作。我是 C# 新手,所以也许我遗漏了什么?? @carpics - 在您的调试器中检查您的 output 变量的类型,这应该会提示您出了什么问题 如果您摆脱了这些可怕的空 catch 块,您将向可靠的解决方案迈出一大步 你没有!你让它“冒泡”直到你真正有能力和上下文处理的有意义的位置。 E.g. in your global.asax's error handler. 【参考方案1】:

适当的解决方案是让这个“一直异步”,正如我在 async best practices 上的 MSDN 文章中所描述的那样。直接调用Result 的问题在于它会导致死锁(正如我在我的博客文章Don't block on async code 中所描述的那样)。 (在您的情况下,Result 的第二个问题是它将MailChimpException 包装在AggregateException 中,使错误处理更加尴尬)。

包装对Task.Run 的调用,然后调用Result 是一种降低Web 服务可扩展性的技巧(也不会处理异常包装的次要问题)。如果您不想使用异步代码,请使用同步 API。由于这是一个 I/O 操作,最自然的方法是异步代码,如下所示:

public async Task<bool> ListSubscribeAsync(string emailAddress, string apiKey, string listId)

  try
  
    var api = new MailChimpManager(apiKey);
    var profile = new Member();
    profile.EmailAddress = emailAddress;
    var output = await api.Members.AddOrUpdateAsync(listId, profile);
    return true;
  
  catch (Exception ex)
  
    logger.Error("An error ocurred in trying to Subscribe to Mailchimp list. 0", ex.Message);
    return false;
  

【讨论】:

好吧,因为我没有真正体验过 c#,我的问题是:如果我让它“一直异步”,那么我最终如何得到结果。我现在希望此方法返回订阅 Mailchimp 的“会员”。该成员在执行后从任务中返回。那么如果我让它“一直异步”我如何得到结果?有些方法我调用只是为了从 Mailchimp 中获取一些东西 @carpics:你使用await来获取异步方法的结果。 确实如此。这就是为什么我让它“一直异步”,所有调用者方法都需要是异步的。但正如我已经说过的,这对我来说目前是不可能的。我正在更新使用“PerceptiveMcApi”作为包装器的现有项目。但这不支持新的 Mailchimp API v3.0。唯一支持的 Mvc.Net 包装器是这个带有异步方法的包装器。所以在我的项目中,我从很多地方调用这些方法,比如 ListSubscribe(),我需要将很多其他方法作为异步。我需要正确地从同步方法调用 asnyc 方法 @carpics:没有合适的方法来进行异步同步。只有各种各样的hack,所有这些都是脆弱的。如果你不能一直异步,那么我建议你一直同步。 @StephenCleary 有大量只提供异步方法的库,以及大量现有代码库无法切换到完全异步的人。似乎默认答案不能只是 - 嘿,您如何重新编写整个应用程序。【参考方案2】:

这是对更新后问题的回答。

您只需在可能引发异常的任务上调用Task.Result 即可获得结果。您的 lambda 表达式必须使用 return await 返回被调用方法的 Task&lt;T&gt; 结果。否则整个表达式的结果将是一个非泛型任务,它表示一个不返回值且没有Result 成员的操作。

static void Main(string[] args)

    try
    
        var task = Task.Run(async () =>  return await AsyncMethod(); );
        Console.WriteLine("Result: " + task.Result);
    
    catch
    
        Console.WriteLine("Exception caught!");
    

    Console.ReadKey();


static async Task<string> AsyncMethod()

    throw new ArgumentException();

    var result = await Task.Run(() =>  return "Result from AsyncMethod"; );
    return result;

请注意,我已删除对 Task.Wait 的调用,因为当您还获取结果时,它是不必要的。

引用自 MSDN 的 Task&lt;TResult&gt;.Result 参考页面:

访问属性的 get 访问器会阻塞调用线程,直到 异步操作完成;相当于调用 等待方法。

代码 sn-p 将显示来自catch 块的文本。如果你从AsyncMethod 中删除第一行,你会得到显示的结果。

【讨论】:

【参考方案3】:

这个:https://blogs.msdn.microsoft.com/ptorr/2014/12/10/async-exceptions-in-c/

长话短说,您永远不会访问异步方法的结果,也永远不会看到异常。如果不想让父方法异步,则需要从 AddOrUpdateAsync 方法返回的任务对象中显式获取.Result 属性的值。

【讨论】:

抱歉,我更新了上面的代码。我实际上是通过 .Result 获得价值,只是不知何故我在问题的代码中忘记了这一点

以上是关于来自异步方法的 Mvc .Net 捕获异常的主要内容,如果未能解决你的问题,请参考以下文章

捕获异步 void 方法引发的异常

ASP.NET MVC 控制器不会从 LINQ lambda 捕获异常

vb.net制作的Excel文件 未捕获通过反射调用的方法而引发的异常

未捕获异步方法中引发的异常 - 为啥?

捕获调用异步方法C#的异常[重复]

是否可以捕获 JavaScript 异步回调中抛出的异常?