Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

HttpClient.GetStringAsync is not executed

The code runs in .NET Standard 2.0. I have a constructor calling a method which will call an Azure function like this:

public ChatViewModel(IChatService chatService)
{
    Task.Run(async () =>
    {
        if (!chatService.IsConnected)
        {
            await chatService.CreateConnection();
        }
    });
}

The method is like this:

public async Task CreateConnection()
{
   await semaphoreSlim.WaitAsync();

   if (httpClient == null)
   {
        httpClient = new HttpClient();
   }

   var result = await httpClient.GetStringAsync(uri);

   var info = JsonConvert.DeserializeObject<Models.ConnectionInfo>(result);

   //... some other code ...
   semaphoreSlim.Release();
}

The code stops at

await httpClient.GetStringAsync(uri)

The URI is 100% valid, if I copy and paste it in the browser I get the JSON I wanted.

When opening up Fiddler, no call has been made to the URI.

EDIT Source code is coming from this Github repo: https://github.com/PacktPublishing/Xamarin.Forms-Projects/tree/master/Chapter06-07/Chat/Chat

And oddly enough I seem to get a call in Azure: enter image description here

EDIT 2 This code is working:

static void Main(string[] args)
{
    using (var httpClient = new HttpClient())
    {
        var a = httpClient.GetStringAsync(uri).GetAwaiter().GetResult();
    }
}

This code is not working:

static void Main(string[] args)
{
    Task.Run(async() => {
        using (var httpClient = new HttpClient())
        {
            var a = await httpClient.GetStringAsync(uri);
        }
    });
}
like image 320
CyclingFreak Avatar asked Sep 18 '19 19:09

CyclingFreak


3 Answers

I guess your issue raises because probably you run it in console application (or windows service) without any code snippet to keeps your app alive.

This code is not working:

static void Main(string[] args)
{
    Task.Run(async() => {
        using (var httpClient = new HttpClient())
        {
            var a = await httpClient.GetStringAsync(uri);
        }
    });
}

This code obviously doesn't work because Task.Run creates a background thread and Main method immediately being finished after that (and respectively it causes your task to be terminated). Just add Console.ReadKey() at the end of Main method to keeps it alive and you will see everything works well:

static void Main(string[] args)
{
    Task.Run(async() => {
        using (var httpClient = new HttpClient())
        {
            var a = await httpClient.GetStringAsync(uri);
        }
    });

    Console.ReadKey();
}

Another option is using new async entry points for Main method which are introduced in C# 8:

static async Task Main(string[] args)
{
    using (var httpClient = new HttpClient())
    {
        var a = await httpClient.GetStringAsync(uri);
    }
}
like image 61
Arman Ebrahimpour Avatar answered Nov 02 '22 18:11

Arman Ebrahimpour


This might be an issue with calling async code in your constructor. You should make use of .GetAwaiter().GetResult() if you really want to run async code at that point:

public ChatViewModel(IChatService chatService)
{
    Task.Run(async () =>
    {
        if (!chatService.IsConnected)
        {
            await chatService.CreateConnection();
        }
    }).GetAwaiter().GetResult();
}

I would prefer a separated async Init method that will be called after the instance was created. Another possible solution would be to create an async GetInfo() method:

private async Task<Models.ConnectionInfo> GetInfo(){
    if(_info != null)
        return _info;

    await semaphoreSlim.WaitAsync();
    try{
        if(_info != null)
            return _info;

        if (httpClient == null)
        {
            httpClient = new HttpClient();
        }

        var result = await httpClient.GetStringAsync(uri);

        var info = JsonConvert.DeserializeObject<Models.ConnectionInfo>(result);

        //... some other code ...

        return _info = info;
    } finally {
        semaphoreSlim.Release();
    }
}

You can call that method to get your info and the code will only be run on first use.

like image 6
Sebastian Avatar answered Nov 02 '22 18:11

Sebastian


Main() method is entry point of you program. After Main method is called program exits, if there are not any foreground threads left running. when you run you download code in new Task, tasks in c# are executed on background thread, that means when Main() method is finished that thread will be aborted. And in you case "Main" method starts download task and finishes instantly because it is not waiting for that task, so download operation is aborted even before request is sent to server. You should wait for task at the end of Main() method.

like image 2
Giorgi Chkhikvadze Avatar answered Nov 02 '22 20:11

Giorgi Chkhikvadze