Làm cách nào để xóa một đối tượng cụ thể, duy nhất khỏi ConcurrentBag <>?


109

Với tính năng mới ConcurrentBag<T>trong .NET 4, làm cách nào để bạn xóa một đối tượng cụ thể, nhất định khỏi nó khi chỉ TryTake()TryPeek()có sẵn?

Tôi đang nghĩ đến việc sử dụng TryTake()và sau đó chỉ cần thêm lại đối tượng kết quả vào danh sách nếu tôi không muốn xóa nó, nhưng tôi cảm thấy có thể mình đang thiếu thứ gì đó. Đây có phải là cách chính xác?

Câu trả lời:


89

Câu trả lời ngắn gọn: bạn không thể làm điều đó một cách dễ dàng.

ConcurrentBag giữ một hàng đợi cục bộ luồng cho mỗi luồng và nó chỉ xem xét hàng đợi của các luồng khác khi hàng đợi của chính nó trở nên trống. Nếu bạn loại bỏ một mục và đặt nó trở lại thì mục tiếp theo bạn xóa có thể lại là mục đó. Không có gì đảm bảo rằng liên tục loại bỏ các mục và đặt chúng trở lại sẽ cho phép bạn lặp lại tất cả các mục.

Hai lựa chọn thay thế cho bạn:

  • Xóa tất cả các mục và ghi nhớ chúng, cho đến khi bạn tìm thấy mục bạn muốn xóa, sau đó đặt lại các mục khác sau đó. Lưu ý rằng nếu hai luồng cố gắng làm điều này đồng thời, bạn sẽ gặp sự cố.
  • Sử dụng cấu trúc dữ liệu phù hợp hơn như ConcurrentDictionary .

9
SynchronizedCollection cũng có thể là một sự thay thế thích hợp.
ILIA BROUDNO

2
@ILIABROUDNO - bạn nên đặt đó là câu trả lời! Đó là SO MUCH tốt hơn so với một kludgey ConcurrentDictionary khi bạn không cần một từ điển
Denis

2
FYI, SynchronizedCollection không khả dụng trong .NET Core. Kể từ ngày nhận xét này, các loại System.Collections.Concurrent là cách hiện tại để triển khai dựa trên .NET Core.
Matthew Snyder

2
Tôi không chắc phiên bản .NET Core nào đang được sử dụng, nhưng tôi đang làm việc trên một dự án dựa trên .NET Core 2.1 SDK và SynchronizedCollection hiện có trong không gian tên Collections.Generic.
Lucas Leblanc

15

Bạn không thể. Nó là một cái túi, nó không được đặt hàng. Khi bạn đặt nó trở lại, bạn sẽ chỉ bị mắc kẹt trong một vòng lặp vô tận.

Bạn muốn một Bộ. Bạn có thể mô phỏng một ứng dụng với ConcurrentDictionary. Hoặc một HashSet mà bạn tự bảo vệ mình bằng khóa.


8
Hãy mở rộng. Bạn sẽ sử dụng cái gì làm khóa trong ConcurrentDictionary bên dưới?
Denise Skidmore

2
Tôi giả sử khóa sẽ là loại đối tượng mà bạn đang cố gắng lưu trữ, và sau đó giá trị sẽ là một tập hợp của một số loại. Điều đó sẽ "mô phỏng" a HashSetnhư anh ấy mô tả.
Mathias Lykkegaard Lorenzen

5

ConcurrentBag rất tuyệt vời để xử lý một Danh sách nơi bạn có thể thêm các mục và liệt kê từ nhiều chuỗi, sau đó cuối cùng vứt nó đi như tên của nó gợi ý :)

Như Mark Byers đã nói , bạn có thể tạo lại một ConcurrentBag mới không chứa mục bạn muốn xóa, nhưng bạn phải bảo vệ nó khỏi nhiều lần truy cập chuỗi bằng cách sử dụng khóa. Đây là một lớp lót:

myBag = new ConcurrentBag<Entry>(myBag.Except(new[] { removedEntry }));

Điều này hoạt động và phù hợp với tinh thần mà ConcurrentBag đã được thiết kế.


9
Tôi cảm thấy câu trả lời này là sai lệch. Nói rõ hơn, điều này KHÔNG cung cấp bất kỳ sự an toàn cho luồng nào trong thao tác Xóa mong muốn. Và việc đặt một chiếc khóa xung quanh nó gần như đánh bại mục đích của việc sử dụng một tập hợp đồng thời.
ILIA BROUDNO

1
Tôi đồng ý. Vâng, để làm rõ một chút, ConcurrentBag được thiết kế để lấp đầy, liệt kê và loại bỏ toàn bộ nội dung của nó khi nó hoàn thành. Bất kỳ nỗ lực nào - bao gồm cả việc đào mỏ - để loại bỏ một vật phẩm sẽ dẫn đến một vụ hack bẩn. Ít nhất thì tôi đã cố gắng đưa ra một câu trả lời, mặc dù cách tốt nhất là sử dụng một lớp thu thập đồng thời tốt hơn, như ConcurrentDictionary.
Larry

4

Đánh dấu là đúng ở chỗ ConcurrentDictionary sẽ hoạt động theo cách bạn mong muốn. Nếu bạn muốn vẫn sử dụng ConcurrentBag , điều sau không hiệu quả với bạn, sẽ giúp bạn đạt được điều đó.

var stringToMatch = "test";
var temp = new List<string>();
var x = new ConcurrentBag<string>();
for (int i = 0; i < 10; i++)
{
    x.Add(string.Format("adding{0}", i));
}
string y;
while (!x.IsEmpty)
{
    x.TryTake(out y);
    if(string.Equals(y, stringToMatch, StringComparison.CurrentCultureIgnoreCase))
    {
         break;
    }
    temp.Add(y);
}
foreach (var item in temp)
{
     x.Add(item);
}

3

Như bạn đề cập, TryTake()là lựa chọn duy nhất. Đây cũng là ví dụ trên MSDN . Reflector cũng không cho thấy các phương pháp quan tâm bên trong ẩn khác.


1
public static void Remove<T>(this ConcurrentBag<T> bag, T item)
{
    while (bag.Count > 0)
    {
        T result;
        bag.TryTake(out result);

        if (result.Equals(item))
        {
            break; 
        }

        bag.Add(result);
    }

}

ConcurrentBaglà một tập hợp không có thứ tự, nhưng mã của bạn mong đợi điều đó bag.TryTakebag.Addhoạt động theo cách FIFO. Mã của bạn giả định rằng bagitem, nó vòng cho đến khi nó tìm thấy itemtrong bag. Câu trả lời chỉ có mã không được khuyến khích, bạn nên giải thích giải pháp của mình.
GDavid

-1

Đây là lớp mở rộng của tôi mà tôi đang sử dụng trong các dự án của mình. Nó có thể xóa một mục duy nhất khỏi ConcurrentBag và cũng có thể xóa danh sách các mục khỏi túi

public static class ConcurrentBag
{
    static Object locker = new object();

    public static void Clear<T>(this ConcurrentBag<T> bag)
    {
        bag = new ConcurrentBag<T>();
    }


    public static void Remove<T>(this ConcurrentBag<T> bag, List<T> itemlist)
    {
        try
        {
            lock (locker)
            {
                List<T> removelist = bag.ToList();

                Parallel.ForEach(itemlist, currentitem => {
                    removelist.Remove(currentitem);
                });

                bag = new ConcurrentBag<T>();


                Parallel.ForEach(removelist, currentitem =>
                {
                    bag.Add(currentitem);
                });
            }

        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }

    public static void Remove<T>(this ConcurrentBag<T> bag, T removeitem)
    {
        try
        {
            lock (locker)
            {
                List<T> removelist = bag.ToList();
                removelist.Remove(removeitem);                

                bag = new ConcurrentBag<T>();

                Parallel.ForEach(removelist, currentitem =>
                {
                    bag.Add(currentitem);
                });
            }

        }
        catch (Exception ex)
        {
            Debug.WriteLine(ex.Message);
        }
    }
}

Thật khó tin rằng nó có thể hoạt động vì bạn đang tạo ConcurrentBag mới trong biến cục bộ, nhưng tôi không chắc. bất kỳ bài kiểm tra?
shtse

3
Không, nó không hoạt động. Nó sẽ hoạt động như thế nào, bạn đang tạo một tham chiếu mới duy nhất. Cái cũ vẫn trỏ đến đối tượng cũ .... Nó sẽ có tác dụng nếu bạn làm việc với "ref"
Thomas Christof

-5
public static ConcurrentBag<String> RemoveItemFromConcurrentBag(ConcurrentBag<String> Array, String Item)
{
    var Temp=new ConcurrentBag<String>();
    Parallel.ForEach(Array, Line => 
    {
       if (Line != Item) Temp.Add(Line);
    });
    return Temp;
}

-13

làm thế nào về:

bag.Where(x => x == item).Take(1);

Nó hoạt động, tôi không chắc hiệu quả ...


Điều này không loại bỏ bất cứ thứ gì khỏi túi. Vật phẩm bạn đang lấy vẫn còn bên trong túi.
Keith

3
cần được "túi = ConcurrentBag mới (bag.Where (x => x = item)!)"
atikot

4
@atikot, dòng đó khiến tôi bật cười
parek
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.