3

I am using this library as wrapper for Mailchimp API v3.0

Now I am having issue to catch Exceptions from methods of this library.

The methods are throwing MailChimpException which is extended from Exception.

The thing is if I run it from Console APP then I am able to catch exception, but when I run it from my Web project, I cannot catch Exception and the code just stuck when exception ocurred.

Here is example of My call:

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;
    }
}

And here is the method from the library which I call:

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);
            }
        }
    }
}

Does anyone has idea why I am able to trigger exception in Console APP but not in Web project. Any suggestion what to check?

UPDATE: If I change my code to use Task.Run like:

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;
    }
}

Then I catch Exception in catch block, but then How I can get result from task when everything is fine???

carpics
  • 2,272
  • 4
  • 28
  • 56
  • 1
    Since `AddOrUpdateAsync()` is marked `async`, don't you need to call it using `await`? e.g. `var output = await api.Members.AddOrUpdateAsync(listId, profile);` – Jason Evans Aug 11 '16 at 10:14
  • @JasonEvans But then I have to set my Method from which I am calling this one as async and I don't want that. I need the results and with using .Result it's working. I am new with c# so maybe I am missing something?? – carpics Aug 11 '16 at 10:21
  • @carpics - In your debugger check the type of your `output` variable, that should give you a hint as to whats going wrong – richzilla Aug 11 '16 at 10:43
  • If you get rid of these awful empty catch blocks you take a _big_ step towards a solid solution. – Uwe Keim Aug 11 '16 at 10:43
  • @richzilla The type of the variable is "Member", custom class from that library – carpics Aug 11 '16 at 10:45
  • @UweKeim The empty catch blocks are there actually to catch the exception. This is just to get it working. How I am going to catch exception if I remove catch blocks?? – carpics Aug 11 '16 at 10:46
  • 1
    You don't! You let it "bubble up" until a _meaningful_ location where you actually have the ability and context to handle. [E.g. in your global.asax's error handler](http://stackoverflow.com/questions/21993758/asp-net-mvc-5-error-handling). – Uwe Keim Aug 11 '16 at 10:52
  • `I cannot catch Exception and the code just stuck when exception ocurred.` <= What does that mean exactly? Define "stuck" and also what Exception is being thrown? Does your code deadlock? In this case its most likely a problem where you are mixing `async` code with "sync" code in the same call stack, you should be using "async all the way". – Igor Aug 11 '16 at 10:55
  • Also marking your Web Api or MVC controller methods with Async and returning Task is usually good when you have calls that access external resources that can take a while like communicating with the MailChimp service. So making this call chain "async all the way" would be good practice. – Igor Aug 11 '16 at 10:57
  • @Igor It means that my method from which I call "ListSubscribe()" never returns anything. In my example above it's Void just to for example but it actually shoud return something. After calling: var output = await api.Members.AddOrUpdateAsync(listId, profile).Result; if Mailchimp returns error nothing is executed after. But calling that from Console APP result in exception being catch in catch block. Making this call chain "async all the way" would be so painful for me, as I would need two days to change my project. – carpics Aug 11 '16 at 11:04

3 Answers3

4

The proper solution is to make this "async all the way", as I describe in my MSDN article on 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). (In your case, a secondary problem from Result is that it will wrap MailChimpException within an AggregateException, making error handling more awkward).

Wrapping the call to Task.Run and then calling Result is a hack that decreases the scalability of your web service (and doesn't take care of the secondary problem of exception wrapping, either). If you don't want to use asynchronous code, then use a synchronous API. Since this is an I/O operation, the most natural approach is asynchronous code, which would look like this:

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;
  }
}
Stephen Cleary
  • 437,863
  • 77
  • 675
  • 810
  • Ok, as I have not really experienced with c#, my questino is: if I make it "async all the way", then how I get the result at the end. I want now this method to return "Member" which is subscribed to Mailchimp. That member is returned from task once it's executed. Then if I make it "async all the way" how I get result?? There are methods which I call only to get something from Mailchimp – carpics Aug 11 '16 at 13:50
  • @carpics: You use `await` to get the result of an asynchronous method. – Stephen Cleary Aug 11 '16 at 14:32
  • indeed. And that's why I have make it make it "async all the way", all caller methods needs to be async. But as I already said that's currently not possible for me. I am updating existing project which used "PerceptiveMcApi" as wrapper. But this doesn't support new Mailchimp API v3.0. and the only Mvc.Net wrapper which support is this one with async methods. So in my project I am calling these methods like ListSubscribe() from a bunch of the places, and I would need make a really bunch of other methods as async. I need properly way to call asnyc method from sync method – carpics Aug 11 '16 at 15:00
  • 1
    @carpics: There is no proper way to do sync-over-async. Only a variety of hacks, all of which are brittle. If you can't go async all the way, then I recommend you go sync all the way. – Stephen Cleary Aug 11 '16 at 15:08
  • 1
    @StephenCleary there are loads of libraries that only offer async methods, and tonnes of people with massive existing codebases that cant just be switched to entirely async. Seems like the default answer cant just be - hey how bout you re-write your whole app. – Jono Apr 14 '21 at 01:09
1

This is an answer to the updated question.

You can get the result simply by calling Task.Result on the task which may throw an exception. You lambda expression has to return the called method's Task<T> result using return await. Otherwise the result of the the whole expression will be a non-generic task, which represents an operation that does not return a value and doesn't have a Result member.

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;
}

Notice that I've removed the call to Task.Wait, because it is unnecessary when you also fetch the result.

Quote from MSDN's Task<TResult>.Result reference page:

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

The code snippet will display the text from catch block. If you remove the first line from AsyncMethod, you will get the result displayed.

Kapol
  • 6,383
  • 3
  • 21
  • 46
0

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

To cut a long story short, you never access the result of your async method, and never see the exception. If you dont want to make the parent method async, then you need to explicitly get the value of the .Result property from the task object returned by AddOrUpdateAsync method.

richzilla
  • 40,440
  • 14
  • 56
  • 86
  • Sorry, I updated my code above. I am actually getting the value with .Result, just somehow I forgot that in the code in my question – carpics Aug 11 '16 at 10:50