Почему событие FormClosing вызывает тупик в задании синхронизированного потока с использованием мониторов? - PullRequest
0 голосов
/ 15 января 2019

Это немного сложно объяснить. У меня есть поток, созданный в WindowsForm, поток не является приоритетом и выполняет свою работу, если никто не использовал ресурс исключительно с помощью Monitor.TryEnter.

Но при закрытии формы я использую событие FormClosing, чтобы остановить обработку, сохранить обработанные данные перед закрытием и т. Д. При попытке взять ресурс с другим Monitor.Enter .. создается блок.

Но забавно то, что следующим образом это работает !! Использование нового / вторичного потока или задачи для блокировки внутреннего потока и выполнения окончательного задания буферизации сохранения.

"Пример минимального, полного и проверяемого:"

namespace WindowsFormsApplication3
{
    using System;
    using System.Drawing;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Windows.Forms;

    public partial class Form1 : Form
    {
        private readonly object _syncRoot = new object();
        private volatile bool _working = false;

        readonly Label lblInfo = new Label();

        public Form1()
        {
            InitializeComponent();

            SuspendLayout();

            lblInfo.Text = "...";
            lblInfo.Location = new Point(0, 0);
            Controls.Add(lblInfo);

            CheckBox chkBox = new CheckBox();
            chkBox.Text = "Use Task inside FormClosing";
            chkBox.AutoSize = true;
            chkBox.Location = new Point(Width / 2 - chkBox.Width / 2, Height / 2 + chkBox.Height);
            chkBox.Click += ChkBox_Click; ;
            Controls.Add(chkBox);

            ResumeLayout(false);

            FormClosing += Form1_FormClosing;
        }

        private CancellationTokenSource _cancellationTokenS;
        private bool _flUseThread = false;

        private void Form1_Load(object sender, EventArgs e)
        {
            _cancellationTokenS = new CancellationTokenSource();
            CancellationToken token = _cancellationTokenS.Token;

            _working = true;
            Task.Run(() =>
            {
                while (_working)
                {
                    if (token.IsCancellationRequested)
                        break;

                    processData(DateTime.Now);
                }
            }, token);
        }

        private void ChkBox_Click(object sender, EventArgs e)
        {
            _flUseThread = ((CheckBox)sender).Checked;
        }

        private bool _alreadySafedClose = false;

        private void Form1_FormClosing(object sender, FormClosingEventArgs e)
        {
            if (_flUseThread)
            {
                if (_alreadySafedClose) return;

                if (MessageBox.Show(this,
                        "Do you really want to cancel?", "Question:",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    e.Cancel = true; // Avoid close form.
                    Enabled = false; // Avoid interact with form.

                    Task.Factory.StartNew(() =>
                    {
                        bool lockWasTaken = false;
                        try
                        {
                            _working = false;

                            // Lock other thread.
                            Monitor.Enter(_syncRoot, ref lockWasTaken);

                            _cancellationTokenS.Cancel();

                            saveBufferResults();

                            MessageBox.Show("OK with Thread");

                        }
                        catch (Exception)
                        {
                            // ignored
                        }
                        finally
                        {
                            // Release lock other thread if already locked.
                            if (lockWasTaken)
                                Monitor.Exit(_syncRoot);

                            Invoke((MethodInvoker)delegate
                            {
                                _alreadySafedClose = true;
                                Close(); //Finally Close.
                            });
                        }
                    });
                }
                else
                    e.Cancel = true;
            }
            else
            {
                if (MessageBox.Show(this,
                        "Do you really want to cancel?", "Question:",
                        MessageBoxButtons.YesNo, MessageBoxIcon.Question) == DialogResult.Yes)
                {
                    bool lockWasTaken = false;
                    try
                    {
                        _working = false;

                        _cancellationTokenS.Cancel();

                        // Lock other thread avoid continue processing.
                        Monitor.Enter(_syncRoot, ref lockWasTaken); // <------ here it stays blocked!!

                        saveBufferResults();

                        MessageBox.Show("OK");
                    }
                    catch (Exception)
                    {
                        Console.WriteLine("<DEBUG> The data could not be processed.!");
                    }
                    finally
                    {
                        // Release lock other thread if already locked.
                        if (lockWasTaken)
                            Monitor.Exit(_syncRoot);
                    }
                }
                else
                    e.Cancel = true;
            }
        }

        private void processData(DateTime data)
        {
            //flag to reduce locks..
            if (!_working) return;

            if (Monitor.TryEnter(_syncRoot))
            {
                try
                {
                    //To escape quickly
                    if (_working)
                        innerProcessFrame(data);
                }
                catch (Exception)
                {
                    Console.WriteLine("<DEBUG> The data could not be processed.!");
                }
                finally
                {
                    Monitor.Exit(_syncRoot);
                }
            }
        }

        private ulong _dataCounter = 0;

        private void innerProcessFrame(DateTime data)
        {
            Thread.Sleep(100);
            Invoke((MethodInvoker)delegate
            {
                lblInfo.Text = data.Millisecond + " " + _dataCounter;
            });
            _dataCounter++;
        }

        private void saveBufferResults()
        {
            Thread.Sleep(1);
        }
    }
}

Я считаю, что FormClosing нарушает контекст синхронизации или блокирует поток работы и поэтому в случае потока работы он никогда не достигает линии, где он освобождает ресурс используя Monitor.Exit, в методе processData (...):

Мой вопрос: почему это происходит только при закрытии формы в событии FormClosing ?, потому что если я помещаю кнопку в форму и помещаю тот же код блокировки, все останавливается правильно, то нет DeadLock, и почему он работает, создавая вторичный поток для выполнения окончательной работы при закрытии формы? и, наконец, если кто-то знает лучше или правильный способ ее решения.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...