Модераторы: Partizan, gambit
  

Поиск:

Ответ в темуСоздание новой темы Создание опроса
> Control.Invoke + Thread.Join = deadlock 
:(
    Опции темы
jonie
Дата 16.10.2008, 20:49 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



Код

    public partial class Form1 : Form
    {
        private Thread th;
        private static bool fStop;
        public Form1()
        {
            InitializeComponent();
            this.Text = "0";
            th = new Thread(ThreadRoutine);
            th.Start(this);
        }

        private void ThreadRoutine(object frm2update)
        {
            Form1 frm = (Form1)frm2update;
            int i = 0;
            while (true)
            {
                lock(this)
                {
                    if (fStop) break;
                }
                updateFrmText(frm, i.ToString());
            }
        }

        delegate void UpdateFrmTextDelegate(Form1 frm, string newtext);

        private void updateFrmText(Form1 frm,string newtext)
        {
            if (frm.InvokeRequired)
            {
                frm.Invoke(new UpdateFrmTextDelegate(updateFrmText), frm, newtext);  // второй (th) поток будет висеть тут
            }
            else
            {
                frm.Text = (int.Parse(frm.Text) + 1).ToString();
            }
        }

        private void button1_Click(object sender, EventArgs e)
        {
            lock(this)
            {
                fStop = true;
            }
            th.Join(); //первый (application) поток будет висеть тут
        }
    }

для тех кто не особо в теме : тут мы получим deadlock при нажатии на кнопку.
это распылвчато написано в MSDN, что-то вроде "никогда не делайте thread.join, если делаете control.invoke"

интересует причины, следствия и пути устранения вышеописанной проблемы


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
mihryak
Дата 16.10.2008, 21:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 731
Регистрация: 28.4.2007
Где: С-Пб

Репутация: 19
Всего: 36



ALARM! Далее написана чушь  smile 

По-моему, всё логично - в методе button1_Click, вызванном синхронно из updateFrmText, ожидается завершение текущего же потока.
Использование BeginInvoke решит проблему - взяв ещё один тред из пула, перенесёт установку текста в него, освободив при этом вызывающий поток. В обработчике нажатия выставится флаг, цикл на следующей итерации это увидит, и поток благополучно завершится
Код

        delegate void UpdateFrmTextDelegate(Form1 frm, string newtext);
        private void updateFrmText(Form1 frm, string newtext)
        {
            if (frm.InvokeRequired)
            {
                frm.Invoke(new UpdateFrmTextDelegate(updateFrmText), frm, newtext);  // здесь синхронно вызван button1_Click
            }
            else
            {
                frm.Text = (int.Parse(frm.Text) + 1).ToString();
            }
        }
        private void button1_Click(object sender, EventArgs e)
        {
            lock (this)
            {
                fStop = true;
            }
            th.Join(); // а здесь ожидаем завершения самого себя
        }


Это сообщение отредактировал(а) mihryak - 16.10.2008, 22:53
PM MAIL ICQ   Вверх
jonie
Дата 16.10.2008, 22:01 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



mihryak BeginThread в данном случае создаст другую проблему - дефицит рессурсов. Например типичная задача : обновления прогресса загрузки. Вы бы не хотели, чтобы обновление отставало?) или я что-то упустил?


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
mihryak
Дата 16.10.2008, 22:52 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 731
Регистрация: 28.4.2007
Где: С-Пб

Репутация: 19
Всего: 36



Перечитал своё объяснение - и сам в шоке, чушь написал smile
Дедлок на самом деле возникает совсем по другой причине - Invoke переносит управление в основной поток, который в данный момент застопорен ожиданием окончания потока, вызвавшего Invoke.

jonie, если говорить о более реальной ситуации, то обновлять прогресс можно гораздо реже - раз в n итераций (подогнать, чтобы раз в секунду-две происходило) 
в таком случае дополнительных расходов почти не будет - уже созданный тред будет браться из пула (здесь с Idsa беседовали), он будет обновлять текст и снова засыпать.

Кстати, если дальше продолжить реальное рассмотрение вопроса, то
Код

lock (this)
 - очень опасная штука, кому-нибудь недальновидному так понравится твоя форма, что он решит тоже использовать её в качестве объекта синхронизации, а это - почти гарантированные дедлоки.
Гораздо спокойней объявить внутреннее поле и использовать его.
Код

private object syncRoot = new object();

public void StopItNow()
{
    lock (syncRoot)
    {
        fStop = true;
    }
}

PM MAIL ICQ   Вверх
jonie
Дата 16.10.2008, 23:04 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



Цитата

Дедлок на самом деле возникает совсем по другой причине - Invoke переносит управление в основной поток, который в данный момент застопорен ожиданием окончания потока, вызвавшего Invoke.
да. проще для понимания (кому-нибудь) это вглядит та (в скобках номера потоков):
Invoke(2) ждет (1), когда он освободится, в это время (1) ждет (2), т.к. работает Join(1).
итого: имеем цикл

mihryak хочется, чтобы компонент, который подписывается на события прогресса, сам решал когда и чего ему обновлять, и, при этом он получал бы уведомления в создавшем его потоке. В общем-то можно просто установить флаг и быть спокойным, типа thread сдохнет сам когда-нибудь, но ведь как-то некрасиво...

lock (this) применен для сокращения кода в примере.


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
PashaPash
Дата 16.10.2008, 23:47 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1233
Регистрация: 3.1.2008

Репутация: 13
Всего: 49



jonie, замени Invoke на BeginInvoke


--------------------
PM MAIL WWW   Вверх
jonie
Дата 16.10.2008, 23:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



насколько я понимаю из msdn этот метод ставит запрос на выполнение в очередь UI потока и не дожидается его выполнения, можно получить странные эффекты, например "продолжение прогресса, хотя данные уже закачены".. что, конечно, очень не хорошо.

"реализма" не будет.

Это сообщение отредактировал(а) jonie - 16.10.2008, 23:57


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
mihryak
Дата 16.10.2008, 23:57 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 731
Регистрация: 28.4.2007
Где: С-Пб

Репутация: 19
Всего: 36



Цитата(jonie @  17.10.2008,  00:04 Найти цитируемый пост)
lock (this) применен для сокращения кода в примере

кто-то может взять на вооружение..

задача ясна - скрыть для пользователя компонента его поточную природу (точнее позволить не задумываться о ней)
всё же считаю BeginInvoke приемлемым решением, с ним можно решить задачу так:
Код

    public delegate void ProgressChangedHandler(int progress);

    class WorkThread
    {
        private readonly Control owner;
        private readonly ProgressChangedHandler handler;

        private Thread thread;
        private bool isRunning = false;
        private int progress = 0;

        private readonly object syncRoot = new object();

        public WorkThread(Control owner, ProgressChangedHandler handler)
        {
            this.owner = owner;
            this.handler = handler;
        }

        public bool Enabled
        {
            get
            {
                lock (syncRoot)
                {
                    return isRunning;
                }
            }
            set
            {
                lock (syncRoot)
                {
                    if (isRunning != value)
                    {
                        isRunning = value;
                        if (isRunning)
                        {
                            thread = new Thread(DoWork);
                            thread.Start();
                        }
                    }
                }
            }
        }

        private void DoWork()
        {
            while(true)
            {
                lock (syncRoot)
                {
                    if (!isRunning)
                    {
                        break;
                    }

                    owner.BeginInvoke(handler, progress);
                    Thread.Sleep(100);
                    progress++;
                }
            }
        }
    }

    public partial class Form1 : Form
    {
        private readonly WorkThread workThread;

        public Form1()
        {
            InitializeComponent();

            workThread = new WorkThread(this, workThread_ProgressChanged);
            FormClosed += new FormClosedEventHandler(Form1_FormClosed);
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            workThread.Enabled = false;
        }

        private void workThread_ProgressChanged(int progress)
        {
            Text = progress.ToString();
        }

        private void button1_Click(object sender, EventArgs e)
        {
            workThread.Enabled = !workThread.Enabled;
        }
    }


вместо пересоздания потока его можно усыплять и будить с помощью AutoResetEvent

Добавлено через 3 минуты и 16 секунд
Цитата(jonie @  17.10.2008,  00:57 Найти цитируемый пост)
насколько я понимаю из msdn этот метод ставит запрос на выполнение в очередь UI потока и не дожидается его выполнения, можно получить странные эффекты, например "продолжение прогресса, хотя данные уже закачены".. что, конечно, очень не хорошо.

да это так, но при использовании дополнительного потока этот реализм  для пользователя и так не очевиден
PM MAIL ICQ   Вверх
jonie
Дата 17.10.2008, 00:17 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



замените
Код

        private void button1_Click(object sender, EventArgs e)
        {
            workThread.Enabled = !workThread.Enabled;
            frm.Text = "BAD_INTEGER";  //<-- добавьте это
        }
и ловите Exception, из-за "недоделанных событий" (ну может еще timeout уменьшить в цикле worker-а)

вот думаю про InterlockedIncrement(decrement) для их подсчета...
---
зы: если уж придираться, то рекомендуют использовать AsyncOperation и метод Post его... ну это сути не менят.

Это сообщение отредактировал(а) jonie - 17.10.2008, 00:24


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
PashaPash
Дата 17.10.2008, 01:44 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1233
Регистрация: 3.1.2008

Репутация: 13
Всего: 49



Цитата(jonie @  16.10.2008,  23:57 Найти цитируемый пост)
"реализма" не будет.

У тебя противоречивые требования:
1. Остановить background-поток и дождаться его завершения в пределах одного обработчика - одного сообщения винды.
2. Сделать релизм - отправить главному потоку несколько сообщений и обработать их до завершении background-потока.
Можешь описать что должно происходить по клику на кнопку? И в каком порядке?
Решение:
1. Отложить изменение гуи до завершения этого обработчика - вариант с BeginInvoke. Не совсем понимаю как при этом можно получить странные эффекты - делегат в BeginInvoke будет вызван сразу же после button1_Click. Проблема - код после th.Join в button1_Click отработает до кода изменения gui background-потока.
2. Не вызывать Join из главного потока, оборачивать его в делегат, делать joinDelegate.BeginInvoke и выносить код стоящий в button1_Click в AyncCallback. Проблема - код AyncCallback будет вызван в отдельном потоке.




--------------------
PM MAIL WWW   Вверх
jonie
Дата 17.10.2008, 09:18 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



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

Идеально: begininvoke (как описано выше), но чтобы после Thread.Join очередь сообщений от BeginInvoke "чистилась".


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
mihryak
Дата 17.10.2008, 09:51 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Опытный
**


Профиль
Группа: Участник
Сообщений: 731
Регистрация: 28.4.2007
Где: С-Пб

Репутация: 19
Всего: 36



Цитата(jonie @  17.10.2008,  01:17 Найти цитируемый пост)
и ловите Exception, из-за "недоделанных событий" (ну может еще timeout уменьшить в цикле worker-а)

Поставил одну миллисекунду, исключения не смог поймать (а почему оно должно быть?), но выставленный руками текст, что не удивительно, заменяется последними BeginInvoke'ами.
PM MAIL ICQ   Вверх
PashaPash
Дата 17.10.2008, 10:10 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1233
Регистрация: 3.1.2008

Репутация: 13
Всего: 49



Цитата(jonie @  17.10.2008,  09:18 Найти цитируемый пост)
о клику на кнопку мне нужно остановить workthread, при этом мне бы хотелось, чтобы уведомления о выполнении workthread были синхронизированны с главным потоком, и не шли после остановки workthread. 

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


--------------------
PM MAIL WWW   Вверх
jonie
Дата 17.10.2008, 13:28 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
****


Профиль
Группа: Завсегдатай
Сообщений: 5613
Регистрация: 21.8.2005
Где: Владимир

Репутация: 22
Всего: 118



mihryak ну да эксепшен эт чет я не то скопипастил, но текст ("BAD_INTEGER") будет заменен. я просто менял process на получение с окна мб, не помню уже точно. суть не меняется 8)

PashaPash  пример ?


--------------------
Что-то не поняли? -> Напейтесь до зеленых человечков... эта сверхцивилизация Вам поможет...
PM MAIL Jabber   Вверх
PashaPash
Дата 17.10.2008, 15:24 (ссылка) | (нет голосов) Загрузка ... Загрузка ... Быстрая цитата Цитата


Эксперт
***


Профиль
Группа: Завсегдатай
Сообщений: 1233
Регистрация: 3.1.2008

Репутация: 13
Всего: 49



jonie, 
Код

using System;
using System.Runtime.Remoting.Messaging;
using System.Threading;
using System.Windows.Forms;

namespace ThreadTest
{
    public partial class Form1 : Form
    {
        public Form1()
        {
            InitializeComponent();
        }

        private AutoResetEvent terminateBackgroundThread = new AutoResetEvent(false);
        bool allowGuiCallsFromBackground;
        Thread backgroundThread;

        private void buttonStart_Click(object sender, EventArgs e)
        {
            backgroundThread = new Thread(ThreadFunc);
            allowGuiCallsFromBackground = true;
            backgroundThread.Start();
            
            buttonStart.Enabled = false;
            buttonStop.Enabled = true;
        }

        public void ThreadFunc()
        {
            bool shouldStop = false;

            do
            {
                if (allowGuiCallsFromBackground)
                {
                    Invoke(new MethodInvoker(delegate
                        {
                            textBox1.AppendText("Background thread working..." + Environment.NewLine);
                        }
                    ));
                }

                shouldStop = terminateBackgroundThread.WaitOne(1000);
            }
            while (!shouldStop);

            if (allowGuiCallsFromBackground)
            {
                Invoke(new MethodInvoker(delegate
                    {
                        textBox1.AppendText("Background thread terminating..." + Environment.NewLine);
                    }
                ));
            }

            Thread.Sleep(2000);

            if (allowGuiCallsFromBackground)
            {
                Invoke(new MethodInvoker(delegate
                {
                    textBox1.AppendText("Background thread terminated" + Environment.NewLine);
                }
                ));
            }
        }

        private void buttonStop_Click(object sender, EventArgs e)
        {
            if (backgroundThread != null)
            {
                terminateBackgroundThread.Set();
                MethodInvoker joinInvoker = new MethodInvoker(backgroundThread.Join);
                textBox1.AppendText("Waiting for Join..." + Environment.NewLine);
                joinInvoker.BeginInvoke(BackgroundThreadStopped, joinInvoker);
                buttonStop.Enabled = false;
            }
        }

        private void BackgroundThreadStopped(IAsyncResult ar)
        {
            AsyncResult aResult = (AsyncResult)ar;
            MethodInvoker temp = (MethodInvoker)aResult.AsyncDelegate;
            temp.EndInvoke(ar);

            if (allowGuiCallsFromBackground)
            {
                Invoke(new MethodInvoker(delegate
                {
                    textBox1.AppendText("Got Background Thread terminated callback" + Environment.NewLine);
                    buttonStart.Enabled = true;
                }));
            }
        }

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (backgroundThread != null)
            {
                allowGuiCallsFromBackground = false;
                terminateBackgroundThread.Set();
            }
        }

        private void Form1_FormClosed(object sender, FormClosedEventArgs e)
        {
            if (backgroundThread != null)
            {
                backgroundThread.Join();
            }
        }
    }
}

Еще блоки c allowGuiCallsFromBackground нужно повыносить в lock, но времени нет smile


--------------------
PM MAIL WWW   Вверх
  
Ответ в темуСоздание новой темы Создание опроса
Прежде чем создать тему, посмотрите сюда:
mr.DUDA
THandle

Используйте теги [code=csharp][/code] для подсветки кода. Используйтe чекбокс "транслит" если у Вас нет русских шрифтов.
Что делать если Вам помогли, но отблагодарить помощника плюсом в репутацию Вы не можете(не хватает сообщений)? Пишите сюда, или отправляйте репорт. Поставим :)
Так же не забывайте отмечать свой вопрос решенным, если он таковым является :)


Если Вам понравилась атмосфера форума, заходите к нам чаще! С уважением, mr.DUDA, THandle.

 
0 Пользователей читают эту тему (0 Гостей и 0 Скрытых Пользователей)
0 Пользователей:
« Предыдущая тема | Общие вопросы по .NET и C# | Следующая тема »


 




[ Время генерации скрипта: 0.0581 ]   [ Использовано запросов: 22 ]   [ GZIP включён ]


Реклама на сайте     Информационное спонсорство

 
По вопросам размещения рекламы пишите на vladimir(sobaka)vingrad.ru
Отказ от ответственности     Powered by Invision Power Board(R) 1.3 © 2003  IPS, Inc.