Bagaimana cara menghapus satu objek tertentu dari ConcurrentBag <>?


109

Dengan baru ConcurrentBag<T>di .NET 4, bagaimana Anda menghapus objek tertentu tertentu darinya saat hanya TryTake()dan TryPeek()tersedia?

Saya berpikir untuk menggunakan TryTake()dan kemudian menambahkan objek yang dihasilkan kembali ke daftar jika saya tidak ingin menghapusnya, tapi saya merasa seperti saya mungkin melewatkan sesuatu. Apakah ini cara yang benar?

Jawaban:


89

Jawaban singkatnya: Anda tidak bisa melakukannya dengan cara yang mudah.

ConcurrentBag menyimpan antrean lokal utas untuk setiap utas dan hanya melihat antrean utas lain setelah antreannya sendiri kosong. Jika Anda menghapus item dan mengembalikannya maka item berikutnya yang Anda hapus mungkin item yang sama lagi. Tidak ada jaminan bahwa menghapus item berulang kali dan mengembalikannya akan memungkinkan Anda untuk mengulang semua item.

Dua alternatif untuk Anda:

  • Hapus semua item dan ingat mereka, sampai Anda menemukan yang ingin Anda hapus, kemudian kembalikan yang lain setelahnya. Perhatikan bahwa jika dua utas mencoba melakukan ini secara bersamaan, Anda akan mengalami masalah.
  • Gunakan struktur data yang lebih cocok seperti ConcurrentDictionary .

9
SynchronizedCollection mungkin juga merupakan pengganti yang cocok.
ILIA BROUDNO

2
@ILIABROUDNO - Anda harus menjawabnya! Itu JAUH lebih baik daripada ConcurrentDictionary kludgey saat Anda tidak membutuhkan Dictionary
Denis

2
FYI, SynchronizedCollection tidak tersedia di .NET Core. Pada tanggal komentar ini, jenis System.Collections.Concurrent adalah cara saat ini untuk pergi untuk implementasi berbasis .NET Core.
Matthew Snyder

2
Saya tidak yakin versi .NET Core mana yang digunakan, tapi saya sedang mengerjakan proyek berdasarkan .NET Core 2.1 SDK dan SynchronizedCollection tersedia di ruang nama Collections.Generic sekarang.
Lucas Leblanc

15

Tidak boleh. Ini tas, tidak dipesan. Saat Anda mengembalikannya, Anda hanya akan terjebak dalam putaran yang tak ada habisnya.

Anda menginginkan satu Set. Anda dapat meniru satu dengan ConcurrentDictionary. Atau HashSet yang Anda lindungi diri Anda dengan kunci.


8
Harap kembangkan. Apa yang akan Anda gunakan sebagai kunci dalam ConcurrentDictionary yang mendasarinya?
Denise Skidmore

2
Nah, saya berasumsi kuncinya adalah jenis objek yang Anda coba simpan, lalu nilainya akan menjadi kumpulan dari beberapa jenis. Itu akan "meniru" a HashSetseperti yang dia gambarkan.
Mathias Lykkegaard Lorenzen

5

ConcurrentBag sangat bagus untuk menangani Daftar di mana Anda dapat menambahkan item dan menghitung dari banyak utas, lalu akhirnya membuangnya seperti yang disarankan oleh namanya :)

Seperti yang dikatakan Mark Byers , Anda dapat membuat ulang ConcurrentBag baru yang tidak berisi item yang ingin Anda hapus, tetapi Anda harus melindunginya dari beberapa untaian yang terkena menggunakan kunci. Ini adalah satu baris:

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

Ini berfungsi, dan sesuai dengan semangat yang telah dirancang untuk ConcurrentBag.


9
Saya merasa jawaban ini menyesatkan. Agar jelas, ini TIDAK memberikan keamanan utas apa pun dalam operasi Hapus yang diinginkan. Dan menempatkan kunci di sekitarnya agak mengalahkan tujuan menggunakan koleksi serentak.
ILIA BROUDNO

1
Saya setuju. Nah, untuk sedikit memperjelas, ConcurrentBag dirancang untuk diisi, dihitung dan dibuang dengan seluruh kontennya ketika selesai. Upaya apa pun -termasuk milik saya- untuk menghapus item akan mengakibatkan peretasan kotor. Setidaknya saya mencoba memberikan jawaban, meskipun yang terbaik adalah menggunakan kelas koleksi konkuren yang lebih baik, seperti ConcurrentDictionary.
Larry

4

Mark benar karena ConcurrentDictionary akan berfungsi sesuai keinginan Anda. Jika Anda masih ingin menggunakan ConcurrentBag berikut ini, bukan pikiran efisien Anda, yang akan membawa Anda ke sana.

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

Seperti yang Anda sebutkan, TryTake()adalah satu-satunya pilihan. Ini juga contoh di MSDN . Reflektor juga tidak menunjukkan metode kepentingan internal tersembunyi lainnya.


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);
    }

}

ConcurrentBagadalah koleksi tidak berurutan, tetapi kode Anda mengharapkannya bag.TryTakedan bag.Addberfungsi dengan cara FIFO. Kode Anda mengasumsikan bahwa bagtermasuk item, itu berputar sampai ditemukan itemdi bag. Jawaban hanya kode tidak disarankan, Anda harus menjelaskan solusi Anda.
GDavid

-1

Ini adalah kelas ekstensi saya yang saya gunakan dalam proyek saya. Itu dapat menghapus satu item dari ConcurrentBag dan juga dapat menghapus daftar item dari tas

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);
        }
    }
}

Sulit dipercaya ini bisa berhasil karena Anda membuat ConcurrentBag baru di variabel lokal, tapi saya tidak yakin. tes apa saja?
shtse8

3
Tidak, itu tidak berhasil. Bagaimana cara kerjanya, Anda hanya membuat referensi baru. Yang lama masih menunjuk ke benda lama .... Ini akan berhasil jika Anda bekerja dengan "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

bagaimana tentang:

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

Berhasil, saya tidak yakin seberapa efisien ...


Ini tidak menghapus apa pun dari tas. Item yang Anda ambil tetap berada di dalam tas.
Keith

3
harus "bag = new ConcurrentBag (bag.Where (x => x! = item))"
atikot

4
@atikot, kalimat itu membuatku tertawa
parek
Dengan menggunakan situs kami, Anda mengakui telah membaca dan memahami Kebijakan Cookie dan Kebijakan Privasi kami.
Licensed under cc by-sa 3.0 with attribution required.