Soru C # iplik güvenli-get getiri mi?


Aşağıdaki kod parçam var:

private Dictionary<object, object> items = new Dictionary<object, object>;
public IEnumerable<object> Keys
{
    get
    {
        foreach (object key in items.Keys)
        {
            yield return key;
        }
    }
}

Bu iplik güvenli mi? Eğer yapmam gerekirse lock döngü etrafında veya yield return?

İşte demek istediğim:

Thread1 erişir Keys özellik, Thread2, alttaki sözlüğe bir öğe ekler. Thread1, Thread2 eklentisinden etkileniyor mu?


25
2017-09-04 13:29


Menşei


İkinci örneğiniz kilitlenecek, bir numara döndürecek ve kilidini açacaktır. Ve kilidi açtıktan sonra yinelemeniz gerekecek. - Guillaume
Oh, haklısın. Sadece bunun kötü bir örnek olduğunu fark ettim. Soruyu düzenleyeceğim. - Albic
Sadece küçük bir ışık saçmak için: Kilitleme yoluyla iplik güvenliği pahalıdır, bu nedenle açıkça talep etmedikçe her eylemi otomatik olarak kilitlemek mantıklı değildir. - Cecil Has a Name
Ayy: Ben bunu kastettim yield-sürüm, her durumda iplik güvenliğini gerektirmediğinde performans kaybettiğiniz için örtülü olarak kilitlenmemelidir. - Cecil Has a Name


Cevaplar:


İş parçacığı ile tam olarak ne demek istiyorsun?

Aynı iş parçacığında olsun olmasın, sözlüğünü değiştirirken kesinlikle değiştirmemelisiniz.

Sözlüğe genel olarak birden fazla iş parçacığında erişiliyorsa, arayan sonuç üzerinde yineleme süresince kilitlenebilmeleri için bir kilidi (tüm erişimi kapsayan aynı şeyi) almalıdır.

EDIT: Düzenlemenize yanıt vermek için asla kilit koduna karşılık gelir. Bir yineleyici bloğu tarafından otomatik olarak çıkarılmış bir kilit yoktur - ve bu nasıl olur? syncRoot bu arada?

Üstelik sadece IEnumerable<TKey> iş parçacığı güvenli de yapmaz - çünkü kilit yalnızca zamanın zamanını etkiler dönen Sıra, üzerinde yinelenen süre boyunca değil.


20
2017-09-04 13:34



Evet, Listenin yerine Sözlük demek istedim. İlk düzenleme yanlıştı (bunun için üzgünüm) ve onu kaldırdım. Soruna beklenen davranışı ekledim. - Albic


Sahnelerin arkasında neler olduğuyla ilgili bu gönderiye göz atın. yield anahtar kelime:

C # verim anahtar kelimesinin perde arkasında

Kısaca - derleyici verim anahtar kelimenizi alır ve işlevselliği desteklemek için IL'de bir sınıf oluşturur. Atlamadan sonra sayfayı kontrol edebilir ve oluşturulan kodu kontrol edebilirsiniz ... ve bu kod, şeyleri güvenli tutmak için iş parçacığı kimliğini izler gibi görünür.


18
2017-09-04 13:34



Gerçekten yararlı bağlantı için +1. - Albic


Tamam, bazı testler yaptım ve ilginç bir sonuç aldım.

Görünen koleksiyonun sayımçısından daha çok bir konu olduğu görülmektedir. yield Anahtar kelime. Sayım (aslında MoveNext yöntem) atar (doğru uygulanırsa) InvalidOperationException çünkü numaralandırma değişti. Göre MoveNext yönteminin MSDN belgeleri beklenen davranış budur.

Bir koleksiyonda numaralandırılması genellikle iş parçacığı için güvenli değildir. yield return ya da değil.


7
2017-09-04 16:41





İnanıyorum ama bunu doğrulayan bir referans bulamıyorum. Herhangi bir iş parçacığı bir yineleyici üzerinde foreach çağırır her zaman, temel IEnumerator yeni bir iş parçacığı yerel * örneği oluşturulmalıdır, bu nedenle iki iş parçacığı üzerinde çakışabilir herhangi bir "paylaşılan" bellek durumu olmamalıdır ...

  • Thread Thread - Referans değişkeni bu iş parçacığı üzerinde bir yöntem yığını çerçevesine genişletildi.

3
2017-09-04 13:39



Evet ama GetEnumerator'ı çağırır ve IEnumerator'ı paylaşırsa ne olur? Konularıyla ne yaptığını açıklamalıdır. - Guillaume


Verim uygulamasının iplik güvenli olduğuna inanıyorum. Gerçekten, bu basit programı evde çalıştırabilirsiniz ve listInt () yönteminin durumunun, diğer iş parçacıklarından kenar etkisi olmadan her iş parçacığı için doğru şekilde kaydedildiğini ve geri yüklendiğini fark edeceksiniz.

public class Test
{
    public void Display(int index)
    {
        foreach (int i in listInt())
        {
            Console.WriteLine("Thread {0} says: {1}", index, i);
            Thread.Sleep(1);
        }

    }

    public IEnumerable<int> listInt()
    {
        for (int i = 0; i < 5; i++)
        {
            yield return i;
        }
    }
}

class MainApp
{
    static void Main()
    {
        Test test = new Test();
        for (int i = 0; i < 4; i++)
        {
            int x = i;
            Thread t = new Thread(p => { test.Display(x); });
            t.Start();
        }

        // Wait for user
        Console.ReadKey();
    }
}

3
2017-09-11 23:31



+1. Ben sadece C # 4.0 derleyici tarafından oluşturulan verim yineleyici durum makineleri iş parçacığı güvenli olduğunu doğruladı. - Glenn Slayden


class Program
{
    static SomeCollection _sc = new SomeCollection();

    static void Main(string[] args)
    {
        // Create one thread that adds entries and
        // one thread that reads them
        Thread t1 = new Thread(AddEntries);
        Thread t2 = new Thread(EnumEntries);

        t2.Start(_sc);
        t1.Start(_sc);
    }

    static void AddEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;

        for (int x = 0; x < 20; x++)
        {
            Trace.WriteLine("adding");
            sc.Add(x);
            Trace.WriteLine("added");
            Thread.Sleep(x * 3);
        }
    }

    static void EnumEntries(object state)
    {
        SomeCollection sc = (SomeCollection)state;
        for (int x = 0; x < 10; x++)
        {
            Trace.WriteLine("Loop" + x);
            foreach (int item in sc.AllValues)
            {
                Trace.Write(item + " ");
            }
            Thread.Sleep(30);
            Trace.WriteLine("");
        }
    }
}

class SomeCollection
{
    private List<int> _collection = new List<int>();
    private object _sync = new object();

    public void Add(int i)
    {
        lock(_sync)
        {
            _collection.Add(i);
        }
    }


    public IEnumerable<int> AllValues
    {
        get
        {
            lock (_sync)
            {
                foreach (int i in _collection)
                {
                    yield return i;
                }
            }
        }
    }
}

2
2017-10-24 17:30



İşte, verimin kendiliğinden güvenli OLMADIĞINI gösteren basit bir örnek. Yazıldığı gibi iş parçacığı güvenlidir, ancak AllValues'deki kilidi (_sync) açıklarsanız, birkaç kez çalıştırarak iş parçacığı güvenli olmadığını doğrulayabilirsiniz. InvalidOperationException alırsanız, iş parçacığı güvenli DEĞİLDİR. - sjp