9

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);
        }
    });
}
Fred
  • 3,365
  • 4
  • 36
  • 57
CyclingFreak
  • 1,614
  • 2
  • 21
  • 40
  • I think a better way of doing this is to make the constructor `private`, and instead have a `public static async` method that constructs the object for you. This way you can await the calls all the way through. – maccettura Sep 18 '19 at 19:51
  • 2
    or better yet use the httpclientfactory pattern in .net core 2.1 – Daniel A. White Sep 18 '19 at 19:52
  • @DanielA.White completely forgot that was a thing! My second comment was going to be on the instantiation of the `HttpClient`. Good call – maccettura Sep 18 '19 at 19:53
  • 1
    `When opening up Fiddler, no call has been made to the uri.` `And oddly enough I seem to get a call in Azure:` I can't see how both of those things could be true. – mjwills Sep 18 '19 at 22:04
  • Fiddler not showing but in Azure insight I do see calls. But I do see failed calls also when I'm not testing. So, this might be an error. Set up HttpClientFactory with AutoFac, gives the same result. And a static factory method is also not ok because I have registered the services in DI. – CyclingFreak Sep 19 '19 at 19:38
  • Your second one is returning the task, not the result and since a and httpClient are local variables they might be destroyed before you try to access the result later.) To make this more like the working example you would need to do var a = await httpClient.GetStringAsync(uri).ConfigureAwait(false).result; – Chris McCowan Sep 20 '19 at 16:30
  • did you ever find a solution? – Natrium Jun 15 '20 at 15:18
  • 1
    @Natrium I'm inclined to say this is a dupe of: https://stackoverflow.com/questions/51283681/the-async-method-is-not-await/51284217#51284217 – Camilo Terevinto Jun 15 '20 at 17:22

6 Answers6

12

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);
    }
}
Arman Ebrahimpour
  • 4,252
  • 1
  • 14
  • 46
  • 2
    I would add that you will probably want to await the created background Task. The most logical intent seems to be to wait until the request has finished, not until the user (if there actually is one) arbitrarily presses a key. – Timo Jun 19 '20 at 09:20
  • 1
    @Timo That press key was just to clarify the problem and somehow acts as `Press any key to exit...` but thanks for your hint – Arman Ebrahimpour Jun 19 '20 at 13:44
6

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.

Sebastian
  • 1,569
  • 1
  • 17
  • 20
2

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.

Dzliera
  • 646
  • 5
  • 15
2

Do not start/initialize any async work in a constructor. Move that out into a separate function and call it.

private readonly IChatService _chatService;

public ChatViewModel(IChatService chatService)
{
   _chatService = chatService;
}

public async Task Initialize()
{
    if (!_chatService.IsConnected)
    {
        await _chatService.CreateConnection();
    }
}

Then in your ChatService, instead of newing up an HttpClient, inject an IHttpClientFactory

private readonly IHttpClientFactory _factory;

public ChatService(IHttpClientFactory factory)
{
    _factory = factory;
}

public async Task CreateConnection()
{
   var httpClient = _factory.CreateClient();

   var result = await httpClient.GetStringAsync(uri);

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

}

In your Console app's Main function, you can either change the signature to public static async Task Main() if you are using modern C#

public static async Task Main()
{
    var serviceCollection = new ServiceCollection();
    serviceCollection.AddHttpClient();
    serviceCollection.AddSingleton<IChatService, ChatService>();
    var services = serviceCollection.BuildServiceProvider();
    var instance = ActivatorUtilities.CreateInstance<MyClass>(services);
    await instance.Run();
}

public class MyClass
{
    private readonly IChatService _chatService;

    public MyClass(IChatService chatService)
    {
        _chatService = chatService;
    }

    public async Task Run()
    {
        var viewModel = new ChatViewModel(_chatService);
        await viewModel.Initialize();
    }

}

Or if you cannot change Main's signature, you can do the following:

public static void Main()
{
    var serviceCollection = new ServiceCollection();
    serviceCollection.AddHttpClient();
    serviceCollection.AddSingleton<IChatService, ChatService>();
    var services = serviceCollection.BuildServiceProvider();
    var instance = ActivatorUtilities.CreateInstance<MyClass>(services);
    instance.Run().GetAwaiter().GetResult();
}
JohanP
  • 5,252
  • 2
  • 24
  • 34
1

If you wanna run your code in console, you can add a while loop, it pauses your code until the result changed and the default value change to something else than "wait".... So simple idea

static void Main(string[] args)
{
    var result ="wait";
    Task.Run(async() => {
        using (var httpClient = new HttpClient())
        {
            result = await httpClient.GetStringAsync(uri);
        }
    });
    // display loading or kinda stuff
    while( result=="wait");

}

Happy coding

Peyman Majidi
  • 1,777
  • 2
  • 18
  • 31
1

So, I see something funny that nobody else has mentioned. It might be related to the problem, but I cannot say for sure. Lets take a look at this.

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

Now, when you are trying to build an app that is very performant using the HttpClient object, you usually create a instance and hold it for multiple sessions, rather than create and tear down the object for each call. There are plenty of other articles online about this, if you are interested in reading more. I usually just create a private static instance and reuse it.

Now, the part that looks a little odd is your using inside of an async call. Normally, a using will auto dispose the object on exit from the block, but I don't know how this will interact with the async keyword. Under the covers there is probably a call back method being created that contains the disposal logic, so I think that it should work, but I have not tried what you are doing.

You also might try making a GetAsync call to see if that can return something where the GetStringAsync is failing. I believe that it is doing a GetAsync, and then just doing the conversion to a string of the result for you. Testing using that method might shed some light on what is happening.

Roger Hill
  • 3,677
  • 1
  • 34
  • 38