C# .NET核心API似乎处于死锁状态
我们有一个在生产中运行的.NET核心API,它可以稳定运行几天甚至几周,然后突然冻结。这种冻结甚至可能一天发生多次,完全是随机的。发生的情况:代码似乎已冻结,不接受任何新请求。没有新的请求被记录,线程数上升到天高,内存稳步上升,直到达到最大值 我创建了一个内存转储来进行分析。它告诉我,大多数线程都在等待某个特定函数的锁被释放,看起来像是死锁。我分析了这个函数,不明白为什么会出现问题。有人能帮我吗?显然,我怀疑AsParallel()是线程不安全的,但互联网说不,它是线程安全的C# .NET核心API似乎处于死锁状态,c#,api,.net-core,azure-web-app-service,C#,Api,.net Core,Azure Web App Service,我们有一个在生产中运行的.NET核心API,它可以稳定运行几天甚至几周,然后突然冻结。这种冻结甚至可能一天发生多次,完全是随机的。发生的情况:代码似乎已冻结,不接受任何新请求。没有新的请求被记录,线程数上升到天高,内存稳步上升,直到达到最大值 我创建了一个内存转储来进行分析。它告诉我,大多数线程都在等待某个特定函数的锁被释放,看起来像是死锁。我分析了这个函数,不明白为什么会出现问题。有人能帮我吗?显然,我怀疑AsParallel()是线程不安全的,但互联网说不,它是线程安全的 public as
public async Task<bool> TryStorePropertiesAsync(string sessionId, Dictionary<string, string> keyValuePairs, int ttl = 1500)
{
try
{
await Task.WhenAll(keyValuePairs.AsParallel().Select(async item =>
{
var doc = await _cosmosDbRepository.GetItemByKeyAsync(GetId(sessionId, item.Key), sessionId) ?? new Document();
doc.SetPropertyValue("_partitionKey", sessionId);
doc.SetPropertyValue("key", GetId(sessionId, item.Key));
doc.SetPropertyValue("name", item.Key.ToLowerInvariant());
doc.SetPropertyValue("value", item.Value);
doc.TimeToLive = ttl;
await _cosmosDbRepository.UpsertDocumentAsync(doc, "_partitionKey");
}));
return true;
}
catch
{
ApplicationInsightsLogger.TrackException(ex, new Dictionary<string, string>
{
{ "sessionID", sessionId },
{ "action", "TryStoreItems" }
});
return false;
}
}
public异步任务TryStorePropertiesAsync(字符串sessionId,字典keyValuePairs,int ttl=1500)
{
尝试
{
wait Task.WhenAll(keyValuePairs.aspallel().Select(异步项=>
{
var doc=await_cosmosdbrespository.GetItemByKeyAsync(GetId(sessionId,item.Key),sessionId)??新文档();
doc.SetPropertyValue(“\u partitionKey”,sessionId);
doc.SetPropertyValue(“key”,GetId(sessionId,item.key));
doc.SetPropertyValue(“name”,item.Key.ToLowerInvariant());
单据设置属性值(“值”,项目值);
doc.TimeToLive=ttl;
等待cosmosdbrespository.upsertdocumentsync(doc,“\u partitionKey”);
}));
返回true;
}
抓住
{
ApplicationInsightsLogger.TrackException(例如,新字典
{
{“sessionID”,sessionID},
{“动作”,“幽会项目”}
});
返回false;
}
}
该代码存在严重问题。对于例如100个项目,它触发100个并发操作,每次4/8。循环中的代码似乎从CosmosDB读取一个文档,设置其所有属性,然后调用一个名为“类似”的方法,而不需要预加载任何内容。如果不知道什么是宇宙假设及其方法,人们只能猜测。虽然在加载/更新周期(可能是无用的)发生时试图锁定东西,但它可能会产生额外的冲突
对于初学者来说,AsParallel()
仅用于数据并行:在内存中划分一些数据并使用尽可能多的工作线程,因为每个分区都有内核来处理。这里没有数据,只是对异步操作的调用。这就是为什么对于100个项目,此代码将触发100个并发任务
这可能会达到任何数量的CosmosDB限制,即使它不会导致并发冲突。这也可能导致网络问题,因为所有这些并发连接都使用同一根电缆
不考虑CosmosDB,对远程服务进行大量调用的正确方法是将它们排队,并使用有限数量的工作人员执行它们。这很容易用.NET实现。代码可以更改为以下内容:
class Payload
{
public string SessionKey{get;set;}
public string Key{get;set;}
public string Name{get;set;}
public string Value{get;set;}
public int TTL{get;set;}
}
//Allow only 10 concurrent upserts
var options=new ExecutionDataflowBlockOptions
{
MaxDegreeOfParallelism = 10
};
var upsertBlock=new ActionBlock<Payload>(myPosterAsync,options);
foreach(var payload in payloads)
{
block.Post(pair);
}
//Tell the block we're done
block.Complete();
//Await for all queued operations to complete
await block.Completion;
你的线程在等待哪个锁?你想做什么?并行执行不能加快错误查询的速度。不过,你可以让它们无限慢。如果执行eg 100 single row SELECTs而不是一次检索所有行的单选,则性能会降低100倍。如果您尝试并行执行此操作,您可能会更快或更慢地完成,因为您的100个单独连接相互冲突,并且可能会更新操作为什么还要使用
aspallel()
?老实说,这不是我的代码。所以我不知道他们为什么使用Parallel.ForEach,但我当然能想到一个原因。我更喜欢循环浏览它们,将任务添加到列表中,然后在最后等待列表。我可以也会这样做,但我想了解是什么导致API停止响应aspallel()
是为了数据并行——使用所有内核处理大量内存中的数据。这里不是这样,没有数据。只是IO操作。它也没有做任何有用的事情,真正的异步操作是由GetItemByKeyAsync
和upsertDocumentSync
执行的。您可以只使用someValues.Select(async…)
我现在所做的只是删除aspallel()并用一个简单的Select替换它。这就成功了,它现在稳定运行了将近两周。这意味着AsParallel()会造成死锁,不应该在异步场景中使用。显然我可以接受,但我仍然不明白为什么它会导致僵局。@Peter我已经在问题的评论中解释过了。它旨在实现数据并行,而不是异步操作。它不会死锁,它使用所有可用的内核来处理数据。这意味着,它也使用当前线程。如果没有asparle()
您的代码将一个接一个地创建任务,但从不等待任务完成,直到等待任务。whalll()
等待所有任务。另一方面,aspallel()
一次创建4个或8个相同的任务,也不用等待它们完成。这是等待任务。当所有的人都阻止了汉克斯·帕纳吉奥蒂斯。您之前的评论确实解释了Asparell与Select以及性能差异,但您最后的评论让我意识到实际出了什么问题。非常感谢你
async Task myPosterAsync(Payload item)
{
try
{
var doc = await _cosmosDbRepository.GetItemByKeyAsync(GetId(item.SessionId, item.Key),
item.SessionId)
?? new Document();
doc.SetPropertyValue("_partitionKey", item.SessionId);
doc.SetPropertyValue("key", GetId(sessionId, item.Key));
doc.SetPropertyValue("name", item.Name);
doc.SetPropertyValue("value", item.Value);
doc.TimeToLive = item.TTL;
await _cosmosDbRepository.UpsertDocumentAsync(doc, "_partitionKey");
catch (Exception ex)
{
//Handle the error in some way, eg log it
ApplicationInsightsLogger.TrackException(ex, new Dictionary<string, string>
{
{ "sessionID", item.SessionId },
{ "action", "TryStoreItems" }
});
}
}