C# Net中的键控锁
我有一个Azure服务总线队列,在那里我接收到1到10条具有相同“密钥”的消息。其中一条消息需要通过长时间运行的操作进行处理。完成后,数据库将被更新,其他消息将对其进行检查。但是,同时,其他消息将重新排队,以便进程不会丢失 但是主要的一点是,这个长时间运行的操作不能针对同一个键同时运行,也不应该多次运行 到目前为止,我得到的是:C# Net中的键控锁,c#,.net,azure,azureservicebus,C#,.net,Azure,Azureservicebus,我有一个Azure服务总线队列,在那里我接收到1到10条具有相同“密钥”的消息。其中一条消息需要通过长时间运行的操作进行处理。完成后,数据库将被更新,其他消息将对其进行检查。但是,同时,其他消息将重新排队,以便进程不会丢失 但是主要的一点是,这个长时间运行的操作不能针对同一个键同时运行,也不应该多次运行 到目前为止,我得到的是: void Main() { Enumerable.Range(1, 1000) .AsParallel()
void Main()
{
Enumerable.Range(1, 1000)
.AsParallel()
.ForAll(async i => await ManageConcurrency(i % 2, async () => await Task.Delay(TimeSpan.FromSeconds(10))));
}
private readonly ConcurrentDictionary<int, SemaphoreSlim> _taskLocks = new ConcurrentDictionary<int, SemaphoreSlim>();
private async Task<bool> ManageConcurrency(int taskId, Func<Task> task)
{
SemaphoreSlim taskLock = null;
try
{
if (_taskLocks.TryGetValue(taskId, out taskLock))
{
if (taskLock.CurrentCount == 0)
{
Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")}, {taskId}, I found. No available.. Thread Id: {Thread.CurrentThread.ManagedThreadId}");
return false;
}
taskLock.Wait();
Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")}, {taskId}, I found and took. Thread Id: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
}
else
{
taskLock = new SemaphoreSlim(1, 1);
taskLock = _taskLocks.GetOrAdd(taskId, taskLock);
if (taskLock.CurrentCount == 0)
{
Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")}, {taskId}, I didn't find, and then found/created. None available.. Thread Id: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
return false;
}
else
{
taskLock.Wait(TimeSpan.FromSeconds(1));
Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")}, {taskId}, I didn't find, then found/created, and took. Thread Id: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
}
}
Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")}, {taskId}, Lock pulled for TaskId {taskId}, Thread Id: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
await task.Invoke();
return true;
}
catch (Exception e)
{
;
return false;
}
finally
{
//taskLock?.Release();
_taskLocks.TryRemove(taskId, out taskLock);
//Console.WriteLine($"I removed. Thread Id: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
}
}
void Main()
{
可枚举范围(11000)
.天冬酰胺()
.ForAll(异步i=>await ManageConcurrency(i%2,异步()=>await Task.Delay(TimeSpan.FromSeconds(10)));
}
私有只读ConcurrentDictionary _taskLocks=新建ConcurrentDictionary();
专用异步任务管理器并发性(int-taskId,Func-Task)
{
信号量lim taskLock=null;
尝试
{
if(_taskLocks.TryGetValue(taskId,out taskLock))
{
if(taskLock.CurrentCount==0)
{
WriteLine($“{DateTime.Now.ToString(“hh:mm:ss.ffffff”)},{taskId},我找到了。没有可用的..线程Id:{Thread.CurrentThread.ManagedThreadId}”);
返回false;
}
taskLock.Wait();
WriteLine($“{DateTime.Now.ToString(“hh:mm:ss.ffffff”)},{taskId},我找到并获取了.Thread Id:{System.Threading.Thread.CurrentThread.ManagedThreadId}”);
}
其他的
{
taskLock=新信号量lim(1,1);
taskLock=\u taskLocks.GetOrAdd(taskId,taskLock);
if(taskLock.CurrentCount==0)
{
WriteLine($“{DateTime.Now.ToString(“hh:mm:ss.ffffff”)},{taskId},我没有找到,然后找到/创建了。无可用..线程Id:{System.Threading.Thread.CurrentThread.ManagedThreadId}”);
返回false;
}
其他的
{
taskLock.Wait(TimeSpan.FromSeconds(1));
WriteLine($“{DateTime.Now.ToString(“hh:mm:ss.ffffff”)},{taskId},我没有找到,然后找到/创建,并获取.Thread Id:{System.Threading.Thread.CurrentThread.ManagedThreadId}”);
}
}
WriteLine($“{DateTime.Now.ToString(“hh:mm:ss.ffffff”)},{taskId},为taskId{taskId}拉锁,线程Id:{System.Threading.Thread.CurrentThread.ManagedThreadId}”);
等待任务。调用();
返回true;
}
捕获(例外e)
{
;
返回false;
}
最后
{
//taskLock?.Release();
_taskLocks.TryRemove(taskId,out taskLock);
//WriteLine($“I removed.Thread Id:{System.Threading.Thread.CurrentThread.ManagedThreadId}”);
}
}
它不能像预期的那样工作,因为它将创建多个信号量,突然我的长时间运行的操作用同一个键运行了两次。我认为问题在于整个操作不是原子的
解决这个问题的最佳方法是什么?你就快到了。。。您需要保留进货订单吗?如果没有:
public static void Main(string[] args)
{
Enumerable.Range(1, 1000)
.AsParallel()
.ForAll( i => ManageConcurrency(i % 2, () => Task.Delay(TimeSpan.FromSeconds(10))).Wait());
}
private static readonly ConcurrentDictionary<int, SemaphoreSlim> _lockDict = new ConcurrentDictionary<int, SemaphoreSlim>();
private static async Task<bool> ManageConcurrency(int taskId, Func<Task> task)
{
var gate = _lockDict.GetOrAdd(taskId, _ => new SemaphoreSlim(1, 1));
await gate.WaitAsync();
try
{
Console.WriteLine($"{DateTime.Now.ToString("hh:mm:ss.ffffff")}, {taskId}, Lock pulled for TaskId {taskId}, Thread Id: {System.Threading.Thread.CurrentThread.ManagedThreadId}");
await task();
return true;
}
catch (Exception e)
{
return false;
}
finally
{
gate.Release();
}
}
publicstaticvoidmain(字符串[]args)
{
可枚举范围(11000)
.天冬酰胺()
.ForAll(i=>ManageConcurrency(i%2,()=>Task.Delay(TimeSpan.FromSeconds(10))).Wait());
}
私有静态只读ConcurrentDictionary _lockDict=新ConcurrentDictionary();
专用静态异步任务管理器并发性(int taskId,Func Task)
{
var gate=_lockDict.GetOrAdd(taskId,=>newsemaphoreslim(1,1));
wait gate.WaitAsync();
尝试
{
WriteLine($“{DateTime.Now.ToString(“hh:mm:ss.ffffff”)},{taskId},为taskId{taskId}拉锁,线程Id:{System.Threading.Thread.CurrentThread.ManagedThreadId}”);
等待任务();
返回true;
}
捕获(例外e)
{
返回false;
}
最后
{
gate.Release();
}
}
您正确地认识到需要确保每个密钥只创建一个信号量。这方面的标准习惯用法是:
var dict = new ConcurrentDictionary<TKey, Lazy<SemaphoreSlim>>();
...
var sem = dict.GetOrAdd( , _ => new new Lazy<SemaphoreSlim>(() => SemaphoreSlim(1, 1))).Value;
var dict=新的ConcurrentDictionary();
...
var sem=dict.GetOrAdd(,=>new-new-Lazy(()=>SemaphoreSlim(1,1))).Value;
可能会创建多个懒惰,但其中只有一个会被揭示和实现
除此之外,依赖内存状态是一种值得怀疑的做法。如果您的队列处理应用程序回收,并且所有信号量都丢失了怎么办?您最好使用持久存储来跟踪此锁定信息。在我看来,由于担心信号量之类的问题,您的生活变得更加困难。有更容易使用的抽象 在这种情况下,使用
Lazy
是理想的选择,但由于您希望等待结果,因此Lazy
需要升级到AsyncLazy
以及需要运行以执行计算的方法:
private LongRunningResult ComputeLongRunningResult(int index)
{
Console.WriteLine($"Running Index {index}");
Thread.Sleep(1000);
return new LongRunningResult() { Index = index };
}
现在我们需要字典来保存惰性异步:
private readonly ConcurrentDictionary<int, AsyncLazy<LongRunningResult>> _results
= new ConcurrentDictionary<int, AsyncLazy<LongRunningResult>>();
谢谢你的回答。我将尝试一下,看看它在我的环境中是如何工作的。为了回答您的问题,如果我的应用程序回收了,那么我必须重新启动长时间运行的操作,从scratchRelated:
private LongRunningResult ComputeLongRunningResult(int index)
{
Console.WriteLine($"Running Index {index}");
Thread.Sleep(1000);
return new LongRunningResult() { Index = index };
}
private readonly ConcurrentDictionary<int, AsyncLazy<LongRunningResult>> _results
= new ConcurrentDictionary<int, AsyncLazy<LongRunningResult>>();
Enumerable
.Range(1, 10)
.AsParallel()
.ForAll(async i =>
{
var index = i % 2;
Console.WriteLine($"Trying Index {index}");
_results.TryAdd(index,
new AsyncLazy<LongRunningResult>(
() => ComputeLongRunningResult(index),
LazyThreadSafetyMode.ExecutionAndPublication));
AsyncLazy<LongRunningResult> ayncLazy;
if (_results.TryGetValue(index, out ayncLazy))
{
await ayncLazy;
}
});
Trying Index 1
Trying Index 0
Trying Index 1
Trying Index 1
Trying Index 0
Trying Index 1
Running Index 1
Trying Index 0
Trying Index 1
Running Index 0
Trying Index 0
Trying Index 0