6

I'm using the following code to get an endpoint and write it to a cache:

public async Task UpdateCacheFromHttp(string Uri)
{
    if (string.IsNullOrEmpty(Uri))
        return;

    var httpClient = new HttpClient();
    var response = await httpClient.GetAsync(Uri);

    if ((response != null) && (response.IsSuccessStatusCode))
    {
        var responseStream = await response.Content.ReadAsStreamAsync();
        WriteToCache(responseStream);
    }
}

The code is running on IIS.

If the endpoint can't be reached I'd expect GetAsync to throw an exception. Even with a Try-Catch, it never seems to fail. GetAsync never returns (I tried a 5 second timeout on the HttpClient, still didn't return).

This does throw an exception:

public Task UpdateCacheFromHttp(string Uri)
{
    var updateCacheTask = Task.Factory.StartNew(new Action(() =>
    {
        if (string.IsNullOrEmpty(Uri))
            return;

        var httpClient = new HttpClient();
        var response = httpClient.GetAsync(Uri).Result;

        if (response.IsSuccessStatusCode)
        {
            var responseStream = response.Content.ReadAsStreamAsync().Result;
            WriteToCache(responseStream);
        }
    }));

    return updateCacheTask;
}

I get the expected "Unable to connect to the remote server".

I suspect it has something to do with the code running in IIS, but why? How do I get it to properly throw the exception without the need to start a new task?

2 Answers 2

19

My intuition tells me that you're calling Wait or Result further up your call stack.

If that is correct, then you're causing a deadlock, as I explain on my blog.

Sign up to request clarification or add additional context in comments.

Comments

0

As I encountered the same behaviour with no Exception being thrown, I created a sample to demonstrate the problem with a possible solution:

using System;
using System.Linq;
using System.Net.Http;
using System.Threading.Tasks;

namespace Exam
{
    public static class Program
    {
        private static async Task<string> GetWebPage(string uri)
        {
            var httpClient = new HttpClient();
            var response = await httpClient.GetAsync(new Uri(uri, UriKind.Absolute), HttpCompletionOption.ResponseContentRead);
            return await response.Content.ReadAsStringAsync();
        }

        public static void Main(string[] args)
        {
            try
            {
                // These two lines do not work - i.e. it terminates the application without any exception being thrown...
                //string s = await GetWebPage(@"https://www.dwd.de/DE/leistungen/klimadatendeutschland/klimadatendeutschland.html");
                //Console.WriteLine(s);

                // This works:
                Task<string> getPageTask = GetWebPage(@"https://www.dwd.de/DE/leistungen/klimadatendeutschland/klimadatendeutschland.html");
                getPageTask.Wait();
                if (getPageTask.IsCompleted)
                    Console.WriteLine(getPageTask.Result);
            }
            catch (AggregateException aex)
            {
                aex.InnerExceptions.AsParallel().ForAll(ex => Console.WriteLine(ex));
            }
            catch (Exception ex)
            {
                Console.WriteLine(ex);
            }
            Console.ReadKey();
        }
    }
}

When you additionally change the URI to something like @"invalid https://...." you will retrieve the AggregateException. Hope, it helps anyone :-)

5 Comments

Uncommenting the line string s = await GetWebPage(... should cause a compile-time error, because the calling method is not async. I.e. I highly doubt that it causes the application to terminate without any exception being thrown.
Hey Theodor, if you add "async" it works as described. I needed to remove "async" to not get the compile error, when the two marked lines are commented ;-)
Cordt if you add async to void Main you get an async void, which is something to avoid. The correct signature for an asynchronous Main() entry point is async Task Main().
Theodor, thanks for the clarification. I know that async void is to be avoided as it does not forward exceptions. I came across this thread because when I was preparing myself for the C# exam I encountered the termination of my program without any exception. So i googled and found this thread. I don't know exactly why this had happen, but now the same piece of code is running without termination. I can only guess that the loading of the page took longer than today and that's why it works.
Yeap, this is a reasonable explanation. Btw the async void methods are not like fire-and-forget tasks that have their exceptions unobserved. An exception thrown by an async void is rethrown on the captured SychronizationContext, or on the ThreadPool if their isn't any, so you are surely going to observe it (the process will be crashed). For this reason the async voids are sometimes called fire-and-crash.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.