Mvc .Net 从异步方法中捕获异常

Mvc .Net Catch Exception from async method

我正在使用 this 库作为 Mailchimp API v3.0

的包装器

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

这些方法正在抛出 MailChimpException,它是从 Exception 扩展而来的。

问题是,如果我从控制台应用程序 运行 它然后我能够捕获异常,但是当我从我的 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 块中捕获异常,但是当一切正常时我如何从任务中获取结果???

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

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

这是对更新问题的回答。

在可能抛出异常的任务上调用Task.Result即可得到结果。您的 lambda 表达式必须使用 return await return 被调用方法的 Task<T> 结果。否则整个表达式的结果将是一个非泛型任务,它表示一个没有 return 值且没有 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<TResult>.Result 参考页:

Accessing the property's get accessor blocks the calling thread until the asynchronous operation is complete; it is equivalent to calling the Wait method.

代码片段将显示来自 catch 块的文本。如果您从 AsyncMethod 中删除第一行,您将得到显示的结果。

正确的 解决方案是"async all the way",正如我在 async best practices. The problem with calling Result directly is that it causes a deadlock (as I describe on my blog post Don't block on async code 上的 MSDN 文章中所描述的那样。 (在你的情况下,Result 的第二个问题是它将 MailChimpException 包装在 AggregateException 中,使错误处理更加尴尬)。

包装对 Task.Run 的调用,然后调用 Result 是一种降低 Web 服务可扩展性的 hack(并且也没有处理异常包装的次要问题) .如果你不想使用异步代码,那么使用一个同步的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;
  }
}