Исключение LockRecursionException в многопоточной функции

#c# #multithreading #exception #readerwriterlockslim #readerwriterlock

Вопрос:

Я не знаю, как это описать, но я получаю исключение, в котором не было бы места, когда код написан хорошо. Это исключение связано с проблемой с ReaderWriterLockSlim, и это исключение LockRecursionException; оно отображается в «ScreenLocker.EnterReadLock();» строка. Не могу найти проблему с моим кодом и описанием, что делать или что может быть не так в Интернете, поэтому я пишу этот вопрос здесь и прошу вас всех о помощи. Это код, с которым у меня проблема:

 public static List<Dictionary<int, int>> RunTasks(ScreenScanning ss)
    {
        var listOfTasks = new List<Task>();
        List<Dictionary<int, int>> PosXOfBlocksAndMeaningOfIt = new List<Dictionary<int, int>>();
        for (var i = 0; i <= BlocksOnYAxisOnScreen; i  )
        {
            ScreenLocker.EnterReadLock();
            var t = new Task(() =>
            {
                PosXOfBlocksAndMeaningOfIt.Add(ss.XAxysScan(PosYOfRowsToScan[i], Screen, ref ScreenLocker));
            });
            listOfTasks.Add(t);
        }
        Task.WaitAll(listOfTasks.ToArray());
        return PosXOfBlocksAndMeaningOfIt;
    }
 

и это функции, вызываемые этим методом:

 public Dictionary<int, int> XAxysScan(int posY, Bitmap screen, ref ReaderWriterLockSlim screenLocker)
    {
        screenLocker.ExitReadLock();
        Dictionary<int, int> partOfMainTable = new Dictionary<int, int>();
        partOfMainTable.Add(666, posY); //used in BotViewUpdate in DataToTableInterpreter
        for (int i = 0; i <= 1920; i  )
        {
            if (screen.GetPixel(i, posY) == ColorsInRow[0])
            {
                if (IsFarmable(posY, ColorsInRow, i, screen))
                {
                    partOfMainTable.Add(i, 1);
                }
            }
            else if (IsBackground(BackgroundColors, i, posY, screen))
            {
                partOfMainTable.Add(i, 0);
            }
            else
            {
                partOfMainTable.Add(i, 2);                
            }
        }
        return partOfMainTable;
    }
 

Как вы можете видеть, что я снимаю блокировку сразу после входа в функцию XAxysScan.

Ответ №1:

Как вы можете видеть, что я снимаю блокировку сразу после входа в функцию XAxysScan.

ReaderWriterLockSlim Это объект синхронизации, который позволяет нескольким потокам считывать данные с ресурса, но разрешает запись в него только 1 ресурсу(в идеале).

Причина, по которой это важно, заключается в том, что конкретный способ, ReaderWriterLockSlim реализуемый для достижения этого эффекта, требует чего-то, называемого сходством управляемых потоков, что в основном означает, что любой Task или поток, который вызвал EnterReadLock() , должен быть тем же Task или вызывающим потоком ExitReadLock(); .

Когда мы смотрим на следующее, мы видим, что вы RunTasks(ScreenScanning ss) ввели блокировку, но вы сразу же запускаете нового ребенка Task и передаете его в ReaderWriterLockSlim качестве ссылки XAxysScan() .

 ScreenLocker.EnterReadLock();
var t = new Task(() =>
{
    PosXOfBlocksAndMeaningOfIt.Add(ss.XAxysScan(PosYOfRowsToScan[i], Screen, ref ScreenLocker));
});
 

Только тот же Task , кто входит в замок, может освободить этот замок. По крайней мере, для подобных объектов синхронизации ReaderWriterLockSlim используйте сходство управляемых потоков.

Подумайте о том, чтобы перейти EnterReadLock() к этому XAxysScan() методу.

 public Dictionary<int, int> XAxysScan(int posY, Bitmap screen, ref ReaderWriterLockSlim screenLocker)
{
    screenLocker.EnterReadLock();
    try{
        Dictionary<int, int> partOfMainTable = new Dictionary<int, int>();
    partOfMainTable.Add(666, posY); //used in BotViewUpdate in DataToTableInterpreter
        for (int i = 0; i <= 1920; i  )
        {
            if (screen.GetPixel(i, posY) == ColorsInRow[0])
            {
                if (IsFarmable(posY, ColorsInRow, i, screen))
                {
                    partOfMainTable.Add(i, 1);
                }
            }
            else if (IsBackground(BackgroundColors, i, posY, screen))
            {
                partOfMainTable.Add(i, 0);
            }
            else
            {
                partOfMainTable.Add(i, 2);                
            }
        }
        return partOfMainTable;
    }
    finally
    {
        // make sure that even if we encounter an error, we still exit the lock so other threads can enter the lock / begin writing
        screenLocker.ExitReadLock();
    }