如何以线程安全的方式关闭窗体(从后台线程使用)

本文关键字:线程 后台 窗体 安全 方式关 | 更新日期: 2023-09-27 18:04:49

我有一个显示数据网格的表单。我还有一个在另一个线程上运行的方法,它只更新网格中显示的单元格。为此,此方法在窗体上调用一个函数,该函数返回显示的单元格。

我遇到的问题是,有时当窗体关闭并释放时,另一个线程上的方法仍在调用此函数,这会导致对象释放异常。有没有办法(除了确保另一个线程上的方法完成之外(来防止这种情况?

因此,我需要一个线程安全的方法来在表单关闭时终止后台任务。

private delegate List<foo> GetShownCellsDelegate();
public List<foo> GetShownCells()
{
  if (this.InvokeRequired)
  {
    GetShownCellsDelegate getShownCellsDelegate = new GetShownCellsDelegate(GetShownCells);
    return (List<foo>)this.Invoke(getShownCellsDelegate);
  }
  else
  {
    //do stuff
  }
}

我尝试使用以下表单的IsDisposed属性:

if (!IsDisposed)
{
  return (List<foo>)this.Invoke(getShownCellsDelegate);
}

但显然,表单可以在if语句之后处理,因为我仍然得到isdisposed异常。

这就是我在另一个线程上使用函数的方式:

private CancellationTokenSource cts = new CancellationTokenSource();
public void CancelUpdate()
{
  cts.Cancel();
}
public void ReadDataFromDevice()
{
  ThreadPool.QueueUserWorkItem(new WaitCallback(ReadAllDataThreadPoolMethod));
}
private void ReadAllDataThreadPoolMethod(Object stateInfo)
{
  if (!cts.IsCancellationRequested)
  {
    //do stuff
  }
}

CancelUpdate方法是从窗体上的IsClosing事件调用的。但有时我仍然会得到isdisposed异常。

如何以线程安全的方式关闭窗体(从后台线程使用)

要取消长期运行的操作,可以使用CancellationToken,它是专门为协作取消而设计的。

让主窗体在启动后台线程时创建一个CancellationTokenSource,将CTS生成的CacellationToken传递给后台线程,在窗体关闭时取消CTS,然后让后台线程在尝试调用回主线程之前检查令牌是否已取消。

public void Foo()
{
    var cts = new CancellationTokenSource();
    var task = Task.Run(() => DoWork(cts.Token));
    FormClosing += (s, args) =>
    {
        cts.Cancel();
        if (!task.IsCompleted)
        {
            args.Cancel = true;
            task.ContinueWith(t => Close());
        }
    };
}
private void DoWork(CancellationToken cancellationToken)
{
    while (!cancellationToken.IsCancellationRequested)
    {
        //Do some  work
    }
}

为了绝对确保后台线程没有通过取消检查,然后屈服于UI线程,让它取消令牌并处理表单,在工作完成之前,您还需要确保后台线程在被取消后,在表单关闭之前有时间运行到完成。这可以通过关闭处理程序中的简单Thread.Join调用来完成。

this.FormClosed += new FormClosedEventHandler(form1_FormClosed);
void form1_FormClosed(object sender, FormClosedEventArgs e)
{
    //close thread
}

无论何时关闭表单,都会执行此操作。