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; } } } 
7
  • 1
    You are calling GetToken in a fire and forget way. Commented Aug 20, 2020 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. Commented Aug 20, 2020 at 11:01
  • @MatthewWatson There is no other solution than making GetAuthorizeToken async too and call GetToken with await? Commented Aug 20, 2020 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? Commented Aug 20, 2020 at 11:35
  • 2
    Does this answer your question? How to call asynchronous method from synchronous method in C#? Commented Aug 20, 2020 at 12:01

1 Answer 1

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()); } } 
Sign up to request clarification or add additional context in comments.

Comments

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.