Закрыть программу при работающих потоках

Работает один или несколько потоков. Нажимаем кнопку “Закрыть”. Программа падает, потому что потоки продолжают работать и очень хотят синхронизироваться с формой, а её больше нет.
Собственно, больной вопрос: Как этого избежать? :man_shrugging:
В потоке можно ловить System.ComponentModel.InvalidAsynchronousStateException, тогда ошибки нет. Но это как-то тупо - на каждый чих ловить экскепшен :thinking:

        public void Work(object context)
        {
            synchronizationContext = (SynchronizationContext)context;
       ...

Как проверить, что поток этого контекста ещё существует?
Но это не главная проблема. Когда форма закрывается, она никого не ждет. А значит, обработчик события завершения потока не выполнится. А в нём может быть что-то важное.

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

Да, только если они

а форма (UI поток) будет ждать их заблокировав поток, то можно и дедлок получить )

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

А как потом продолжить закрытие? :thinking:

Ну так за потоками-то следить надо, а не просто запустил и забыл )
Например добавлять их в какой-нибудь список, при завершении потока удалять его из списка, смотреть не пуст ли.

А следить как? Знаю, что при закрытии формы можно проверять список. Если он не пуст, то не давать закрыться. Это самая примитивная защита от дурака. Но потоки ведь могут вовсе не завершиться и форму закрыть вообще не получится.
А как ещё за ними следить? :man_shrugging:

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

Ну так пусть ведут себя лучше )

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

То есть, при закрытии формы отменяем закрытие и переключаем какой-нибудь флаг в true. А при завершении потока, если этот флаг true пытаемся закрыться. А там опять проверяется список, и так пока список не пуст.
Так чтоли? :thinking:

Например, подключение к какому-нибудь URL иногда может занимать 40 секунд. И что тогда делать? А бывает, что вообще напрочь виснет.

CancellationToken выставляете и все. В циклах каждого из потоков делаете условие

While(!CancelToken.CancelRequested)
{
do work
}

При закрытии формы в лоб выставляете CancelRequest и все кто связан с этим объектом автоматически выйдет из своих циклов работы. Зачем тут что то придумывать с фалагми.

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

Это видимо не для самих потоков, а для

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

Разве при этом не может возникнуть ситуация, когда разные потоки одновременно пишут и читают этот CancellationToken? :thinking:

А при этом главный поток будет ждать завершения всех остальных? :thinking: Если нет, то куда денется проблема синхронизации, описанная в первом посте? :thinking:

Он специально разработан для оповещения. Если один раз его выставить как requested то обратно его уже не вернуть. Это потокобезопасная замена вашему флагу bool.

А чего нет то. Делаете дополнительный флаг типа ProgramClosing; При закрытии программа проверяет этот флаг. Если он истина значит подразумевается что программа сама все закончила и завершает работу без запроса пользователя.

То есть. Завершаете прогу с флагом false, программа видит что это пользователь завершает, проверяет потоки если они работают то отменяет закрытие и выдает диалог ожидания, в тоже время сообщает любым способом о требовании закрыться… либо это будет токен который изначально передан в потоки либо это какой то другой способ. После того как потоки завершили работу программа выставляет флаг ProgramClosing в истину и закрывается. В обработчике закрытия видим что флаг в истине значит не тормозим его и завершаем всю работу приложения.

А кто этот флаг в true установит? То есть, при завершении потока удалять его из списка, и если список пуст ставить true. А перед запуском потоков делать false.
Правильно понял?

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

Что это ещё за функция такая? :thinking:

Обычная такая функция …

void KilThemAll()
{
// тут куод обработки
}

А контроллировать-то как? :thinking: Надо же не просто всех убить и закрыться, а подождать завершения и только тогда закрыться.
Если просто кинуть CancelRequested, то форма всё-равно закроется сразу и не будет ждать, пока завершатся все потоки.

А как именно она узнает, что все потоки завершились?

Как как … обычно …

List<Thread> tasks = new List<Thread>();

        CancellationTokenSource source = new CancellationTokenSource();

        private void button3_Click_1(object sender, EventArgs e)
        {
            Label[] labs = new Label[] { label1, label2, label3, label4, label5, label6 };

            for (int i = 0; i < labs.Length; i++)
            {
                int idx = i;
                Thread thr = new Thread(() =>
                {
                    Random rnd = new Random((int)DateTime.Now.Ticks);
                    int count = 0;
                    while (!source.IsCancellationRequested)
                    {
                        Thread.Sleep(rnd.Next(0, 500));
                        this.Invoke(new MethodInvoker(() => labs[idx].Text = "Work " + (count++).ToString()));
                    }
                    this.Invoke(new MethodInvoker(() => labs[idx].Text = "Cancel working ...  " + (count++).ToString()));
                    Thread.Sleep(5000);
                    this.Invoke(new MethodInvoker(() => labs[idx].Text = "Canceled!"));
                });
                tasks.Add(thr);
                thr.Start();
            }
        }
        #endregion

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (!source.IsCancellationRequested)
            {
                if(tasks.Count(s => s.ThreadState == System.Threading.ThreadState.Stopped) != tasks.Count)
                {
                e.Cancel = true;
                WaitForm form = new WaitForm(this);
                form.Show();
                Task.Factory.StartNew(() =>
                {
                    while (tasks.Count(s => s.ThreadState == System.Threading.ThreadState.Stopped) != tasks.Count)
                    {
                        Thread.Sleep(1000);
                    }
                    form.Invoke(new MethodInvoker(() => form.lab.Text = "Все закрылись. Закрываем прогу"));
                    Thread.Sleep(2000);
                    form.Invoke(new MethodInvoker(() => form.Close()));
                    this.BeginInvoke(new MethodInvoker(() => this.Close()));
                });
                }
                source.Cancel();              
            }
        }

gifcompressor.zip (7.9 МБ)

А с обычным потоком (не таской) так можно?