Подтвердить что ты не робот

Доходность возвращается в инструкции блокировки

Если у меня есть доходность возврата в операторе блокировки, то блокировка извлекается при каждом выходе (5 раз в примере ниже) или только один раз для всех элементов в списке?

Спасибо

    private List<string> _data = new List<string>(){"1","2","3","4","5"};
    private object _locker =new object();
    public IEnumerable<string> GetData()
    {
        lock (_locker)
        {
            foreach (string s in _data)
            {
                yield return s;
            }
        }
    }
4b9b3361

Ответ 1

Изменить: Этот ответ был неправильным, но я не могу удалить его, поскольку он был помечен как правильный. Пожалуйста, см. @Lockszmith ответ ниже для правильного ответа.

перефразировать:

Блокировка НИКОГДА не освобождается между каждым возвратом yeald. ПРИМЕЧАНИЕ. Однако он освобождается, когда перечислитель выполнен, то есть когда цикл foreach завершается.

Редактировать конец

Оригинальный ответ (неверный):

В вашем сценарии блокировка будет выполняться только один раз. Короче говоря, только один раз. Однако вы не имеете никаких общих ресурсов. Когда вы начинаете работать с общими ресурсами, например, в консольном приложении ниже, происходят некоторые интересные вещи.

Вы увидите по результатам, что блокировка временно освобождается при каждом выходе. Также обратите внимание, что блокировка в списке 1 не освобождается до тех пор, пока все элементы не будут записаны на консоль, показывая, что метод GetData() выполняется частично с каждой итерацией цикла и что блокировка должна быть временно выпущена с каждой выход.

    static void Main(string[] args)
    {
        object locker = new object();
        IEnumerable<string> myList1 = new DataGetter().GetData(locker, "List 1");
        IEnumerable<string> myList2 = new DataGetter().GetData(locker, "List 2");
        Console.WriteLine("start Getdata");
        foreach (var x in myList1)
        {
            Console.WriteLine("List 1 {0}", x);
            foreach(var y in myList2)
            {
                Console.WriteLine("List 2 {0}", y);
            }
        }
        Console.WriteLine("end GetData");
        Console.ReadLine();
    }

    public class DataGetter
    {
        private List<string> _data = new List<string>() { "1", "2", "3", "4", "5" };

        public IEnumerable<string> GetData(object lockObj, string listName)
        {
            Console.WriteLine("{0} Starts", listName);
            lock (lockObj)
            {
                Console.WriteLine("{0} Lock Taken", listName);
                foreach (string s in _data)
                {
                    yield return s;
                }
            }
            Console.WriteLine("{0} Lock Released", listName);
        }
    }
}

Результаты:

            start Getdata
            List 1 Starts
            List 1 Lock Taken
            List 1 1
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 2
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 3
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 4
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 5
            List 2 Starts
            List 2 Lock Taken
            List 2 1
            List 2 2
            List 2 3
            List 2 4
            List 2 5
            List 2 Lock Released
            List 1 Lock Released
            end GetData

Однако, он действительно здорово, вот результаты. Обратите внимание, что строка "start GetData" возникает после вызова метода DataGetter(). GetData(), но перед тем, что происходит в методе GetData(). Это называется отложенным исполнением, и оно демонстрирует красоту и полезность оператора return yield: в любом месте внутри вашего внешнего цикла вы можете выйти из цикла, и больше не будет вызовов во внутренний цикл. Это означает, что вам не нужно перебирать весь внутренний цикл, если вам это не нужно, и это также означает, что вы начнете получать результаты в свой внешний цикл раньше.

Ответ 2

НАЧАТЬ EDIT
Пожалуйста, ознакомьтесь с кодом в вики сообщества, предоставленным @EZI, который легче читать/чистить.
END EDIT

Извините, что воскресил это из мертвых, но, прочитав принятый ответ Даниэля, а затем сам тестировал это, хотя, по крайней мере, эти 10 человек, участвующих в голосовании, должны по крайней мере знать, что это совершенно неправильно.

Ответ: Блокировка НИКОГДА не освобождается между каждым yeald return.
ПРИМЕЧАНИЕ. Однако он освобождается, когда перечислитель выполнен, то есть когда цикл foreach завершается.

Ответ Дэниела неверен, утверждая, что блокировка выполняется более одного раза. Это потому, что код Daniel не многопоточен, он всегда будет вычислять одинаково. Блокировка в этом коде берется только один раз, и поскольку он тот же поток, он всегда один и тот же.

Я взял код @Даниэля из его ответа и изменил его для работы с 2 потоками, один для List1 и другой поток, созданный для каждой итерации List2.

Как вы можете видеть, как только поток t2 запущен, потоки будут заблокированы, так как t2 ждет блокировки, которая никогда не будет выпущена.

Код:

void Main()
{
    object locker = new object();
    IEnumerable<string> myList0 = new DataGetter().GetData(locker, "List 0");
    IEnumerable<string> myList1 = new DataGetter().GetData(locker, "List 1");
    IEnumerable<string> myList2 = new DataGetter().GetData(locker, "List 2");

    Console.WriteLine("start Getdata");
    // Demonstrate that breaking out of a foreach loop releasees the lock
    var t0 = new Thread(() => {
        foreach( var s0 in myList0 )
        {
            Console.WriteLine("List 0 {0}", s0);
            if( s0 == "2" ) break;
        }
    });
    Console.WriteLine("start t0");
    t0.Start();
    t0.Join(); // Acts as 'wait for the thread to complete'
    Console.WriteLine("end t0");

    // t1 foreach loop will start (meaning previous t0 lock was cleared
    var t1 = new Thread(() => {
        foreach( var s1 in myList1)
        {
            Console.WriteLine("List 1 {0}", s1);
            // Once another thread will wait on the lock while t1 foreach
            // loop is still active a dead-lock will occure.
            var t2 = new Thread(() => {
                foreach( var s2 in myList2 )
                {
                    Console.WriteLine("List 2 {0}", s2);
                }
            } );
            Console.WriteLine("start t2");          
            t2.Start();
            t2.Join();
            Console.WriteLine("end t2");            
        }
    });
    Console.WriteLine("start t1");
    t1.Start();
    t1.Join();
    Console.WriteLine("end t1");
    Console.WriteLine("end GetData");
}

void foreachAction<T>( IEnumerable<T> target, Action<T> action )
{
    foreach( var t in target )
    {
        action(t);
    }
}

public class DataGetter
{
    private List<string> _data = new List<string>() { "1", "2", "3", "4", "5" };

    public IEnumerable<string> GetData(object lockObj, string listName)
    {
        Console.WriteLine("{0} Starts", listName);
        lock (lockObj)
        {
            Console.WriteLine("{0} Lock Taken", listName);
            foreach (string s in _data)
            {
                yield return s;
            }
        }
        Console.WriteLine("{0} Lock Released", listName);
    }
}

Ответ 3

@У Lockszmith есть хороший улов (+1). Я только публикую это, так как я нахожу его код трудным для чтения. Это "вики сообщества". Не стесняйтесь обновлять.

object lockObj = new object();

Task.Factory.StartNew((_) =>
{
    System.Diagnostics.Debug.WriteLine("Task1 started");
    var l1 = GetData(lockObj, new[] { 1, 2, 3, 4, 5, 6, 7, 8 }).ToList();
}, TaskContinuationOptions.LongRunning);

Task.Factory.StartNew((_) =>
{
    System.Diagnostics.Debug.WriteLine("Task2 started");
    var l2 = GetData(lockObj, new[] { 10, 20, 30, 40, 50, 60, 70, 80 }).ToList();
}, TaskContinuationOptions.LongRunning);

public IEnumerable<T> GetData<T>(object lockObj, IEnumerable<T> list)
{
    lock (lockObj)
    {
        foreach (T x in list)
        {
            System.Diagnostics.Debug.WriteLine("Thread " + Thread.CurrentThread.ManagedThreadId + " returned "  + x );
            Thread.Sleep(1000);
            yield return x;
        }
    }
}