5

SemaphoreSlim.WaitAsync is not working. It jumps to return currentToken.AccessToken before the GetAccesTokenAsync call it's finished and throws NullException. I tried to use also AsyncLock, AsyncSemaphore and some other methods I read online but it seems like nothing it's working in my case.

public static class HttpClientHelper
{
    #region members
    private static SemaphoreSlim semaphore = new SemaphoreSlim(1, 1);
    private static Token currentToken;
    #endregion

    public static string GetAuthorizeToken(ref HttpClient client, string username, string password)
    {
        GetToken(client, username, password);

        return currentToken.AccessToken;
    }

    private static async void GetToken(HttpClient client, string username, string password)
    {
        await semaphore.WaitAsync();

        try
        {
            if (currentToken == null)
            {
                await GetAccesTokenAsync(client, username, password);
            }
            else if (currentToken.IsExpired)
            {
                await GetAccessTokenByRefreshToken(client);
            }
        }
        finally
        {
            semaphore.Release();
        }
    }

    private static async Task<Token> GetAccesTokenAsync(HttpClient client, string username, string password)
    {
        List<KeyValuePair<string, string>> requestBody = new List<KeyValuePair<string, string>>();
        requestBody.Add(new KeyValuePair<string, string>("Username", username));
        requestBody.Add(new KeyValuePair<string, string>("Password", password));
        requestBody.Add(new KeyValuePair<string, string>("grant_type", "password"));

        try
        {
            using (var urlEncodedContent = new FormUrlEncodedContent(requestBody))
            {
                var httpResponse = await client.PostAsync(new Uri(client.BaseAddress + "/api/authentication/token"), urlEncodedContent);
                currentToken = await httpResponse.Content.ReadAsAsync<Token>(new[] { new JsonMediaTypeFormatter() });
            }

            return currentToken;
        }
        catch (Exception e)
        {
            Logers.Log.Error($"Error while getting the access token {e}");
            return null;
        }
    }
}
GSerg
  • 76,472
  • 17
  • 159
  • 346
Catleen
  • 81
  • 1
  • 5
  • 1
    You are calling `GetToken` in a [fire and forget](https://stackoverflow.com/q/46053175/11683) way. – GSerg Aug 20 '20 at 11:00
  • 4
    Your `GetToken()` is a void async method which means that you can't `await` it - and it will return immediately when it hits the first `await` in it. – Matthew Watson Aug 20 '20 at 11:01
  • @MatthewWatson There is no other solution than making GetAuthorizeToken async too and call GetToken with await? – Catleen Aug 20 '20 at 11:25
  • There is a solution if you want to call an async method from somewhere where you can't await it (in other words, changing an async call into a normal synchronous call) - do you want me to post an example as an answer? – Matthew Watson Aug 20 '20 at 11:35
  • @MatthewWatson yes, please. Thank you! – Catleen Aug 20 '20 at 11:41
  • 2
    Does this answer your question? [How to call asynchronous method from synchronous method in C#?](https://stackoverflow.com/questions/9343594/how-to-call-asynchronous-method-from-synchronous-method-in-c) – GSerg Aug 20 '20 at 12:01
  • @Catleen Is there a reason why you cant make `GetAuthorizeToken` async? That is by far the best solution. – Johnathan Barclay Aug 20 '20 at 12:38

1 Answers1

3

The first thing you need to do is to change your private static async void GetToken() method declaration to return a Task: private static async Task GetToken(). If it doesn't return a Task, you won't be able to wait for it to complete. ("async void" is "fire-and-forget" as mentioned by GSerg.)

The most basic way to call an async method from a sync method is to use Task.Run(...).Wait(), as shown below.

Pay attention to the call to Task.Run(waitForSem).Wait(); which is what actually turns the async call into a sync call.

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            Parallel.Invoke(() => timeWaitForSem(1), () => timeWaitForSem(2));
            Console.ReadLine();
        }

        static void timeWaitForSem(int id)
        {
            var sw = Stopwatch.StartNew();
            Console.WriteLine($"Thread {id} is waiting for semaphore.");
            Task.Run(waitForSem).Wait(); // <=== HERE is the important bit.
            Console.WriteLine($"Thread {id} finished waiting for semaphore after {sw.Elapsed}.");
        }

        static async Task waitForSem()
        {
            await _semaphore.WaitAsync().ConfigureAwait(false);
            // Keep hold of the semaphore for a while.
            await Task.Delay(2000).ConfigureAwait(false); 
            _semaphore.Release();
        }

        static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
    }
}

The output of this program will be something like:

Thread 1 is waiting for semaphore.
Thread 2 is waiting for semaphore.
Thread 1 finished waiting for semaphore after 00:00:02.0133882.
Thread 2 finished waiting for semaphore after 00:00:04.0316629.

What you must NOT do is to simply put waitForSem().Wait(); rather than Task.Run(waitForSem).Wait();, because you are likely to get a deadlock that way (particularly if it's called from an application with a message pump, such as WinForms).

For more information, see Calling async methods from non-async code

An alternative and slightly more efficient approach is to use JoinableTaskFactory from Microsoft.VisualStudio.Threading.dll. To use that, you'd need to reference Microsoft.VisualStudio.Threading.dll or add it via NuGet.

This has the advantage of not starting a new thread if it does not need to. If you use JoinableTaskFactory, the code would look like this:

using System;
using System.Diagnostics;
using System.Threading;
using System.Threading.Tasks;
using Microsoft.VisualStudio.Threading;

namespace ConsoleApp1
{
    class Program
    {
        static void Main()
        {
            Parallel.Invoke(() => timeWaitForSem(1), () => timeWaitForSem(2));
            Console.ReadLine();
        }

        static void timeWaitForSem(int id)
        {
            var sw = Stopwatch.StartNew();
            Console.WriteLine($"Thread {id} is waiting for semaphore.");

            _jtf.Run(async () => await waitForSem().ConfigureAwait(false));

            Console.WriteLine($"Thread {id} finished waiting for semaphore after {sw.Elapsed}.");
        }

        static async Task waitForSem()
        {
            await _semaphore.WaitAsync().ConfigureAwait(false);
            // Keep hold of the semaphore for a while.
            await Task.Delay(2000).ConfigureAwait(false);
            _semaphore.Release();
        }

        static readonly SemaphoreSlim _semaphore = new SemaphoreSlim(1, 1);
        static readonly JoinableTaskFactory _jtf = new JoinableTaskFactory(new JoinableTaskContext());
    }
}
Matthew Watson
  • 104,400
  • 10
  • 158
  • 276