问题背景:
目前,我正在学习如何使用the.NET 6 Parallel.ForEachAsync循环进行并发操作。
我已经创建了下面的程序,它具有两个任务在Task.WhenAll函数中并行运行。KeepAliveAsync方法在HttpClient调用中运行最多3级的并行性,总共包含10项。DisposeAsync方法将从并发字典中删除这些项,但在此之前,CleanItemBeforeDisposal方法将删除项对象的属性值。
代码:
{
[TestClass]
public class DisposeTests
{
private ConcurrentDictionary<int, Item> items = new ConcurrentDictionary<int, Item>();
private bool keepAlive = true;
[TestMethod]
public async Task Test()
{
//Arrange
string uri = "https://website.com";
IEnumerable<int> itemsToAdd = Enumerable.Range(1, 10);
IEnumerable<int> itemsToDispose = Enumerable.Range(1, 10);
foreach (var itemToAdd in itemsToAdd)
{
items.TryAdd(itemToAdd, new Item { Uri = uri });
}
//Act
await Task.WhenAll(KeepAliveAsync(), DisposeAsync(itemsToDispose));
//Assert
Assert.IsTrue(items.Count == 0);
}
private async Task KeepAliveAsync()
{
HttpClient httpClient = new HttpClient();
do
{
ParallelOptions parallelOptions = new()
{
MaxDegreeOfParallelism = 3,
};
await Parallel.ForEachAsync(items.ToArray(), parallelOptions, async (item, token) =>
{
var response = await httpClient.GetStringAsync(item.Value.Uri);
item.Value.DataResponse = response;
item.Value.DataResponse.ToUpper();
});
} while (keepAlive == true);
}
private async Task DisposeAsync(IEnumerable<int> itemsToRemove)
{
var itemsToDisposeFiltered = items.ToList().FindAll(a => itemsToRemove.Contains(a.Key));
ParallelOptions parallelOptions = new()
{
MaxDegreeOfParallelism = 3,
};
await Parallel.ForEachAsync(itemsToDisposeFiltered.ToArray(), parallelOptions, async (itemsToDispose, token) =>
{
await Task.Delay(500);
CleanItemBeforeDisposal(itemsToDispose);
bool removed = items.TryRemove(itemsToDispose);
if (removed == true)
{
Debug.WriteLine($"DisposeAsync - Removed item {itemsToDispose.Key} from the list");
}
else
{
Debug.WriteLine($"DisposeAsync - Did not remove item {itemsToDispose.Key} from the list");
}
});
keepAlive = false;
}
private void CleanItemBeforeDisposal(KeyValuePair<int, Item> itemToDispose)
{
itemToDispose.Value.Uri = null;
itemToDispose.Value.DataResponse = null;
}
}
}问题:
代码运行,但我遇到了一个问题,即Item对象的Uri属性在CleanItemBeforeDisposal方法中被设置为null,设计时从Dispose方法调用,但是在并行KeepAliveAsync方法中进行HttpClient调用,此时共享项对象为null,错误有:
System.InvalidOperationException: An invalid request URI was provided. Either the request URI must be an absolute URI or BaseAddress must be set.我在共享的ToArray上使用了ConcurrentDictionary方法,因为我相信这将在调用字典时创建字典的快照,但显然这不能解决这个争用条件。
如何处理两个进程访问一个共享列表的情况,其中一个进程更改了另一个进程所需的该列表实体的属性?
发布于 2022-10-11 17:57:24
我将尽量直接回答这个问题,而不深入了解设计等细节。
ConcurrentDictionary是线程安全的,这意味着多个线程可以安全地从字典中添加和删除项。这种线程安全性根本不适用于字典中存储为值的任何对象。
如果多个线程具有对Item实例的引用并正在更新其属性,则可能会发生各种不可预测的事情。
为了直接回答这个问题:
如何处理两个进程访问一个共享列表的情况,其中一个进程可能已经更改了另一个进程所需的该列表实体的属性?
没有正确的方法来处理这种可能性。如果希望代码以可预测的方式工作,则必须消除这种可能性。
看起来,您可能希望这两个操作能够保持同步。他们不会的。即使他们这样做了,只要一次,它可能永远不会再次发生。这是不可预测的。
如果您实际上需要将Uri和Response设置为null,那么最好在使用这些值之后立即对同一个线程中的每个Item执行该操作。如果你做这三件事
执行对单个Item
在同一个线程中,...one接二连三地出现,它们不可能出现故障。
(但您需要将它们设置为空吗?)还不清楚那是干什么用的。如果你不这么做,就不会有什么问题需要解决。)
https://stackoverflow.com/questions/74020797
复制相似问题