来自异步方法的Mvc .Net Catch异常

时间:2016-08-11 09:46:47

标签: c# asp.net-mvc exception exception-handling mailchimp

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

现在我遇到了从该库的方法中捕获异常的问题。

这些方法正在抛出从MailChimpException扩展的Exception

问题是如果我从Console APP运行它然后我能够捕获异常,但是当我从我的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);
            }
        }
    }
}

有没有人知道为什么我能够在Console APP中触发异常而不是在Web项目中触发异常。有什么建议要检查吗?

更新 如果我将代码更改为使用Task.Run,请执行以下操作:

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

SO:

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块中捕获Exception,但是当一切正常时我如何从任务中获得结果???

3 个答案:

答案 0 :(得分:4)

正确的解决方案是让这个“异步”,正如我在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;
  }
}

答案 1 :(得分:1)

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

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

  

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

代码段将显示catch块中的文字。如果从AsyncMethod中删除第一行,则会显示结果。

答案 2 :(得分:0)

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

简而言之,您永远不会访问异步方法的结果,也永远不会看到异常。如果您不想使父方法异步,那么您需要从AddOrUpdateAsync方法返回的任务对象中显式获取.Result属性的值。