【问题标题】:How to close a form (used from a background thread) in thread safe manner?如何以线程安全的方式关闭表单(从后台线程使用)?
【发布时间】:2014-07-11 15:54:42
【问题描述】:

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

我遇到的问题是,有时当表单已关闭并处置时,另一个线程上的方法仍在调用此函数,这会导致 objectdisposed 异常。有没有办法(除了确保另一个线程上的方法完成)来防止这种情况发生?

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

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
  }
}

从表单上的 IsClosing 事件调用 CancelUpdate 方法。但有时我仍然会遇到 isdisposed 异常。

【问题讨论】:

    标签: c# winforms background thread-safety threadpool


    【解决方案1】:

    要取消长时间运行的操作,您可以使用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 调用来完成。

    【讨论】:

    • 我编辑了我的问题以展示我是如何使用它的。有时我仍然会遇到异常。
    • @Marc.O 非常正确。我希望这应该是一个艰难的比赛条件,但它就在那里。见编辑。
    • 但是你会阻塞线程直到 UI 线程完成?我认为在您关闭整个程序之前不会发生这种情况?
    • @Marc.O 当然。因此,防止死锁发生的唯一方法是取消关闭事件,直到后台线程有时间运行完成。再次查看编辑。
    • 这种方式效果很好。没有更多例外,但现在我们正在等待后台任务完成后再关闭表单,我的问题是是否有其他方法。
    【解决方案2】:
    this.FormClosed += new FormClosedEventHandler(form1_FormClosed);
    void form1_FormClosed(object sender, FormClosedEventArgs e)
    {
        //close thread
    }
    

    这将在您的表单关闭时执行。

    【讨论】:

    • 虽然是这样,但这并没有解释如何将相关信息传达给后台线程。
    • 你想让我用这个做什么?
    猜你喜欢
    • 1970-01-01
    • 2012-07-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-08-12
    • 1970-01-01
    • 2021-05-29
    相关资源
    最近更新 更多