Как сделать потокобезопасный вызов PictureBox.Image в c #, в настоящее время выдает одну из 3 ошибок - PullRequest
2 голосов
/ 12 сентября 2010

Я использую этот PictureBox в форме, в этом графическом окне я использую код AForge.Я передаю ССЫЛКУ pictureBox в класс веб-камеры, который я создаю, который инициализирует веб-камеру и сообщает ей, куда рисовать ее кадры ... так что он счастливо рисует ее кадры ... без проблем.

Но тогдаопределенное время (когда я хочу что-то сделать с указанным изображением, если нажата кнопка chck) ... я запускаю этот таймер, используя простой код:

timer1.Enabled = true;

этот интервал таймера установлен равным 33.

Так что теперь он запускается и каждый раз через цикл мой код имеет следующее:

private void timer1_Tick(object sender, EventArgs e)
{
    ...
    double value = detector.ProcessFrame(new Bitmap(picCapture.Image)); //errors here
    ...

        TimerCallback tc = new TimerCallback(sendDataFast);
        System.Threading.Timer t = new System.Threading.Timer(tc, null, 2000, Timeout.Infinite);

}

В этой строке выше есть одна из трех ошибок, которые я видел (трассировки стека, где это возможно):

Object is currently in use elsewhere.

Out of Memory. (A first chance exception of type 'System.OutOfMemoryException' occurred in System.Drawing.dll)

Parameter not valid (A first chance exception of type 'System.ArgumentException' occurred in System.Drawing.dll)

Я уверен, что это проблемы с многопоточностью, но я понятия не имею, как их решать...Я полностью потерянЕсли программа зависает в этой строке выше, я обычно могу нажать снова запустить в отладчике, и все в порядке.Но я не хочу быть неряшливым и просто поймать уловку, которая продолжается.Я хотел бы выяснить корень этой проблемы ..

Я видел где-то еще, кто-то сказал, что это может быть проблема с многопоточностью и поставить эту строку: System.Diagnostics.Debug.Assert (! This.InvokeRequired,"InvokeRequired");

Так что я сделал в начале этого метода time1_click, но кажется, что assert не происходит, но я не уверен, что это было правильное место для assert ... является timer1_clickв потоке пользовательского интерфейса или нет?

Теперь я подозреваю, что я проверил свой код на предмет того, как я инициализирую класс своей веб-камеры:

Или в пределах этого timer1_click я также вызываю этот метод:

void sendDataFast(Object stateObject)
{
    EmergencyDelegate delEmergency =
           new EmergencyDelegate(mic.sendDataEmergency);

    // call the BeginInvoke function! //sendDataEmergency takes in a picture Image picImage as an argument.
    delEmergency.BeginInvoke(picCapture.Image, null, null);
}

И для полноты вот как я инициализирую класс своей веб-камеры:

        webcam = new WebCam();
        webcam.InitializeWebCam(ref picCapture, ref picComparator, ref dataObject, this);            //guessing this is calling threading issues        

Эти три ошибки, которые происходят не сразу, похоже, происходят случайно, одна изтри .... заставляет меня думать, что это проблема многопоточности, но как еще я могу это исправить?по какой-то причине создаете делегат, который возвращает это двойное значение и вызывается, если invoke required is true?

Ответы [ 3 ]

2 голосов
/ 12 сентября 2010

является ли timer1_click в потоке пользовательского интерфейса или нет?

Зависит от того, какой таймер вы используете. sendDataFast определенно не потому, что вы использовали System.Threading.Timer.

Если вы посмотрите документацию MSDN по System.Threading.Timer, вы увидите следующее

System.Threading.Timer является простым, легкий таймер, который использует обратный вызов методы и обслуживается пул потоков потоки. Не рекомендуется для использования с Windows Forms, потому что его обратные вызовы не происходят на пользователя интерфейсная нить. System.Windows.Forms.Timer лучше выбор для использования с Windows Forms.choice для использования с Windows Forms.

Это объясняет, почему вы зависаете.

Метод обратного вызова, выполняемый таймер должен быть реентерабельным, потому что он вызывается в потоках ThreadPool. обратный вызов может быть выполнен одновременно на два потока потоки, если интервал таймера меньше чем время, необходимое для выполнения обратный вызов, или если весь пул потоков потоки используются и обратный вызов в очереди несколько раз.

Это означает, что если ваша функция не будет выполнена в течение менее 33 мс, функция будет вызвана снова. Это, вероятно, тот случай, и почему вы получаете исключения, которые вы видите. Два или более потоков пытаются использовать один и тот же файл. У вас также может быть несколько потоков, пытающихся выделить большой блок памяти, и один не может получить блок запрошенного вами размера. Не уверен, почему вы получаете исключение аргумента, но это может быть из-за двух предыдущих.

По этой причине я предпочитаю System.Timers.Timer. У него есть свойство AutoReset, которое устанавливает false. Затем в конце своей функции я вызываю Timer.Start. Вы можете сделать то же самое с другими таймерами, но это немного обманщик.

Вот три ссылки, которые могут оказаться полезными

Статья о сравнении классов таймеров

Джон Скит Ответ на вопрос о начале вызова

Блог Эрика Липперта о вероятности исключения OutOfMemory

1 голос
/ 12 сентября 2010

Для OutOfMemoryException я бы предложил заменить

double value = detector.ProcessFrame(new Bitmap(picCapture.Image));

с

double value;
using(Bitmap bmp = new Bitmap(picCapture.Image)) {
    value = detector.ProcessFrame(bmp);
}

поэтому ваше временное растровое изображение будет располагаться так, как должно быть.

1 голос
/ 12 сентября 2010

Я думаю, что, увидев эту строку

double value = detector.ProcessFrame(new Bitmap(picCapture.Image)); 

, вы пытаетесь изменить изображение picCapture.Image, которое является изображением Picturebox, и вы делаете это каждые 33 миллисекунды.Detector.ProcessFrame делать?

2- Вы должны передать фактическое URI изображения в New Bitmap, а не использовать Image, который является источником PictureBox

3- Почему вы создаете больше таймеровтик событие ????

...