問題背景:
我目前正在學習使用 .NET 6Parallel.ForEachAsync回圈的并發操作。
我創建了以下程式,該程式具有兩個在一個Task.WhenAll函式中相互并行運行的任務。該方法對總共十個專案KeepAliveAsync的呼叫運行高達 3 度的并行度。HttpClient該DisposeAsync方法將從并發字典中洗掉這些專案,但在此之前,該CleanItemBeforeDisposal方法會洗掉 Item 物件的屬性值。
代碼:
{
[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;
}
}
}
問題:
代碼運行,但我遇到了一個問題,Uri即 Item 物件的屬性被設定為 null 從CleanItemBeforeDisposal方法中呼叫的Dispose方法,但隨后HttpClient呼叫是在并行KeepAliveAsync方法中進行的,此時共享 Item 物件是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因為我相信這會在呼叫它時創建字典的快照,但顯然這不會解決這種競爭條件。
處理兩個行程正在訪問一個共享串列的情況的正確方法是什么,其中一個行程可能更改了另一個行程需要的該串列的物體的屬性?
uj5u.com熱心網友回復:
我將嘗試直接回答問題,而不涉及設計等細節。
ConcurrentDictionary是執行緒安全的,這意味著多個執行緒可以安全地從字典中添加和洗掉專案。該執行緒安全性根本不適用于作為值存盤在字典中的任何物件。
如果多個執行緒參考一個實體Item并更新其屬性,則可能會發生各種不可預知的事情。
直接回答問題:
處理兩個行程正在訪問一個共享串列的情況的正確方法是什么,其中一個行程可能更改了另一個行程需要的該串列的物體的屬性?
沒有正確的方法來處理這種可能性。如果您希望代碼以可預測的方式作業,您必須消除這種可能性。
看起來您可能希望這兩個操作以某種方式保持同步。他們不會。即使他們這樣做了,只是一次,它可能永遠不會再次發生。這是不可預測的。
如果您確實需要將Uriand設定Response為 null,最好Item在使用完這些值后立即對同一執行緒中的每個執行此操作。如果你做到了這三件事
- 執行單個請求
Item - 用價值觀做事
- 將它們設定為空
......在同一個執行緒中一個接一個,它們不可能發生亂序。
(但是您是否需要將它們設定為null?尚不清楚這是為了什么。如果您不這樣做,那么就沒有問題需要解決。)
轉載請註明出處,本文鏈接:https://www.uj5u.com/caozuo/517879.html
