这是我的建议。对于每个同步方法,都应该添加一个异步方法。例如方法FireTheGun是同步的:
private static void FireTheGun(int bulletsCount)
{
var ratata = Enumerable.Repeat("Ta", bulletsCount).Prepend("Ra");
Console.WriteLine(String.Join("-", ratata));
}
异步对应的FireTheGunAsync 非常简单,因为将同步操作排队的复杂性委托给了辅助方法QueueAsync。
public static Task FireTheGunAsync(int bulletsCount)
{
return QueueAsync(FireTheGun, bulletsCount);
}
这里是QueueAsync的实现。每个动作都有其专用的SemaphoreSlim,以防止多个并发执行:
private static ConcurrentDictionary<MethodInfo, SemaphoreSlim> semaphores =
new ConcurrentDictionary<MethodInfo, SemaphoreSlim>();
public static Task QueueAsync<T1>(Action<T1> action, T1 param1)
{
return Task.Run(async () =>
{
var semaphore = semaphores
.GetOrAdd(action.Method, key => new SemaphoreSlim(1));
await semaphore.WaitAsync();
try
{
action(param1);
}
finally
{
semaphore.Release();
}
});
}
使用示例:
FireTheGunAsync(5);
FireTheGunAsync(8);
输出:
Ra-Ta-Ta-Ta-Ta-Ta
Ra-Ta-Ta-Ta-Ta-Ta-Ta-Ta-Ta
使用不同数量的参数实现QueueAsync 的版本应该很简单。
更新:我之前的QueueAsync 实现可能具有不受欢迎的行为,即以随机顺序执行操作。发生这种情况是因为第二个任务可能是第一个获取信号量的任务。下面是一个保证正确执行顺序的实现。在高竞争的情况下性能可能会很差,因为每个任务都会进入一个循环,直到它以正确的顺序获取信号量。
private class QueueInfo
{
public SemaphoreSlim Semaphore = new SemaphoreSlim(1);
public int TicketToRide = 0;
public int Current = 0;
}
private static ConcurrentDictionary<MethodInfo, QueueInfo> queues =
new ConcurrentDictionary<MethodInfo, QueueInfo>();
public static Task QueueAsync<T1>(Action<T1> action, T1 param1)
{
var queue = queues.GetOrAdd(action.Method, key => new QueueInfo());
var ticket = Interlocked.Increment(ref queue.TicketToRide);
return Task.Run(async () =>
{
while (true) // Loop until our ticket becomes current
{
await queue.Semaphore.WaitAsync();
try
{
if (Interlocked.CompareExchange(ref queue.Current,
ticket, ticket - 1) == ticket - 1)
{
action(param1);
break;
}
}
finally
{
queue.Semaphore.Release();
}
}
});
}