title: Why does the FormClosing event produce a deadlock in a synchronized thread job using Monitors?
body: <p>This is a bit difficult to explain. I have a thread instantiated within a WindowsForm, the thread is not a priority and does its job if nobody has taken the resource exclusively using Monitor.TryEnter.</p>
  
  <p>But when closing the form I use the FormClosing event to stop the processing, save the processed data before closing, etc. When trying to take the resource with another Monitor.Enter .. the block is produced.</p>
  
  <p>But the funny thing is that in the following way it works!!. Using a new/secondary thread or task to block the internal thread and perform the final job of save buffering.</p>
  
  <p>"Minimal, Complete, and Verifiable example:"</p>
  
  <pre><code>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(() =&gt;
              {
                  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(() =&gt;
                      {
                          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); // &lt;------ here it stays blocked!!
  
                          saveBufferResults();
  
                          MessageBox.Show("OK");
                      }
                      catch (Exception)
                      {
                          Console.WriteLine("&lt;DEBUG&gt; 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("&lt;DEBUG&gt; 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);
          }
      }
  }
  </code></pre>
  
  <p>I believe that FormClosing is breaking the synchronization context or blocking the thread of work and
  therefore in the event of the thread of work it never reaches the line where it releases the resource
  using Monitor.Exit, in the processData(...) method:</p>
  
  <p>My question is why does this happen only when closing the form within the FormClosing event?, because
  if I put a button on the form and put the same blocking code everything stops correctly there is no DeadLock, and why does it work creating a secondary thread to do the final work when closing the form ?, and finally if someone knows a better or correct way to solve it.</p>