carpics carpics - 3 months ago 24
C# Question

Mvc .Net Catch Exception from async method

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???

Answer

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.