问题描述
SemaphoreSlim.WaitAsync
不起作用。在return currentToken.Accesstoken
调用完成之前,它跳到GetAccesTokenAsync
并抛出NullException。我还尝试使用AsyncLock,AsyncSemaphore和其他一些在线阅读的方法,但对于我来说似乎没有任何作用。
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 password)
{
await semaphore.WaitAsync();
try
{
if (currentToken == null)
{
await GetAccesTokenAsync(client,password);
}
else if (currentToken.IsExpired)
{
await GetAccesstokenByRefreshToken(client);
}
}
finally
{
semaphore.Release();
}
}
private static async Task<Token> GetAccesTokenAsync(HttpClient client,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;
}
}
}
解决方法
您需要做的第一件事是更改您的private static async void GetToken()
方法声明以返回任务:private static async Task GetToken()
。如果没有返回任务,您将无法等待它完成。 (如GSerg所述,“异步无效”是“一劳永逸”。)
从同步方法中调用异步方法的最基本方法是使用Task.Run(...).Wait()
,如下所示。
请注意对Task.Run(waitForSem).Wait();
的调用,实际上是将异步调用转换为同步调用。
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);
}
}
该程序的输出将类似于:
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.
您绝对不能简单地放置waitForSem().Wait();
而不是Task.Run(waitForSem).Wait();
,因为这样很可能会导致死锁(特别是如果它是从带有消息泵的应用程序中调用的,例如WinForms)。
有关更多信息,请参见Calling async methods from non-async code
另一种更有效的方法是使用Microsoft.VisualStudio.Threading.dll
中的JoinableTaskFactory。要使用它,您需要引用Microsoft.VisualStudio.Threading.dll
或通过NuGet添加它。
这样做的好处是不需要启动新线程。如果您使用JoinableTaskFactory
,代码将如下所示:
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());
}
}