首页 > 解决方案 > 线程仍在尝试访问已处理的表单?

问题描述

基本上,我有一个线程可以下载并将下载状态报告到进度条和标签。即使之前调用了 a ,它在尝试以已处置形式(进度条和标签)调用对象时总是崩溃,并且即使在同一个 try 块中调用if (!this.Disposed)a 仍然会引发异常。catch (ObjectDisposedException)我不确定我能做些什么来解决这个问题,最好将它描述为我遇到过的最烦人的事情。

谢谢。


更新(来自一个体贴的 SO 潜伏者)我在 pastebin 上找到的来源

Thread downloader();
public bool abortThread = false();

private void frmDownload_FormClosing(object sender, FormClosingEventArgs e) {
    downloader.Abort(); // Abort the thread before closing the form...?
    abortThread = true; // Set the abortThread to true
    this.Dispose(); // Dispose thread
}

downloader = new Thread(() => {
    string[] URLs = { "http://test1.com/", "http://test2.com/", "http://test3.com/" };
    try {
        using (WebClient wc = new WebClient()) {
            wc.DownloadProgressChanged += (s, e) => {
                if (!pbDownloadStatus.IsDisposed && !lbPercentage.IsDisposed) {
                    if (!abortThread) {
                        this.Invoke((MethodInvoker)(() => pbDownloadStatus.Value = e.ProgressPercentage)); // EXCEPTION HAPPENS HERE
                        this.Invoke((MethodInvoker)(() => pbDownloadStatus.Value++));
                        this.Invoke((MethodInvoker)(() => pbDownloadStatus.Value--));
                        this.Invoke((MethodInvoker)(() => lbPercentage.Text = e.ProgressPercentage.ToString() + "%"));
                        }
                    }
                };
                wc.DownloadFileCompleted += (s, e) => {
                    if (!pbDownloadStatus.IsDisposed && !lbPercentage.IsDisposed) {
                        lock (e.UserState) {
                            this.Invoke((MethodInvoker)(() => pbDownloadStatus.Value = 0));
                            this.Invoke((MethodInvoker)(() => lbPercentage.Text = "0%"));
                            Monitor.Pulse(e.UserState);
                        }
                    }
                };
                wc.Proxy = WebProxy.GetDefaultProxy();
                wc.Headers.Add(header);
                for (int i = 0; i < URLs.Length; i++) {
                    var sync = new Object();
                    lock (sync) {
                        wc.DownloadFileAsycn(new Uri(URLs[i]), "C:\Test\URL" + i);
                        Monitor.Wait(sync);
                    }
                }
            }
        }
    }
    catch (ObjectDisposedException disEx) { // Never gets caught
        downloader.Abort();
        MessageBox.Show("Object was disposed");
    }
});
downloader.Start();

标签: c#

解决方案


来自 Msdn

从 .NET Framework 4 开始,使用 System.Threading.Tasks.Parallel 和 System.Threading.Tasks.Task 类、Parallel LINQ (PLINQ)、System.Collections.Concurrent 命名空间中的新并发集合类大大简化了多线程编程,以及基于任务概念而非线程概念的新编程模型

在现代,直接使用线程的需求大大减少了,你可能应该看看任务可以取消Tasksasync/await更容易管理,异步返回到调用上下文。

其次,你的代码没有意义,而且它充满了编译器错误,这不是一个好的开始。此外,由于代码有很多错误,我决定只给你很多点来考虑反对重写它

因此,让我们看看这段代码存在的一些更明显的问题。

  1. abortThread从多个线程访问,并且在大多数情况下不是线程安全的。
  2. 你直接打电话var sync = new Object();之前lock (sync)意味着你没有锁定任何东西
  3. 即使该锁定语句将起作用,也没有其他线程使用该锁定,这意味着它是多余的。
  4. 您正在this.Dispose()从表单关闭事件中调用。这至少可以说是不寻常的

    • 表单未在 Close 上处理的两种情况是: (1) 它是多文档界面 (MDI) 应用程序的一部分,并且表单不可见;(2) 您已经使用 ShowDialog 显示了表单。在这些情况下,您将需要手动调用 Dispose 以将表单的所有控件标记为垃圾回收。

    • 基本上在非 MDI 应用程序中,如果您调用,ShowDialog则将其放在 using 语句中。

  5. 您正在尝试检查是否IsDisposed可以安全地编组回 UI 线程。

    • 仅仅因为您打电话Dispose并不意味着表格已被处置,这不是它的工作方式,也不会解决您的问题。
  6. 如果您需要执行异步 IO 绑定工作,请使用aysnc,await模式,那么您不会因为等待完成端口的虚假原因而阻塞线程。如果您需要并行运行它,请考虑使用带有操作块的 DataFlow,以便您可以利用aysnc,await和并行。
  7. 如果您需要确定表单是活动的还是已死的,请使用线程安全变量。使用静态锁定对象,并且在您更新变量的每个地方也使用锁定。
  8. 如果您需要编组到 UI 线程。不要this.Invoke((MethodInvoker)(()多次执行,一次执行,一次更新所有内容。

推荐阅读