【问题标题】:Global static variables using Thread vs using Task使用 Thread 与使用 Task 的全局静态变量
【发布时间】:2016-10-14 09:49:11
【问题描述】:

假设我有一个 FormWithLabel(只是带有标签的表单以显示当前长期操作的状态),我会在我的主应用程序表单中运行一些“繁重”任务时显示它。为此,我有静态变量:

public static FormWithLabel loadingForm = null;

在“繁重”任务的情况下,我在单独的线程中创建 FormWithLabel 并显示它,直到日志操作结束。当我使用线程时 - 没关系:

Thread loadingFormThread = new Thread(new ThreadStart(() =>
    {
        loadingForm = new FormWithLabel();
        loadingForm.ShowDialog();
    }
    ));

    loadingFormThread.SetApartmentState(ApartmentState.STA);
    loadingFormThread.Start();
...
//some "heavy" work which is done separate thread and updates some visual data in main UI via Invoke()
...
if (loadingForm != null)
    {
        loadingForm.Dispose();
        loadingForm = null;
    }

但是当我使用任务而不是线程时

new Task(() =>
    {
         loadingForm = new FormWithLabel();
         loadingForm.ShowDialog();
    }).Start();
...
//some "heavy" work which is done separate thread and updates some visual data in main UI via Invoke()
...
if (loadingForm != null)
    {
        loadingForm.Dispose();
        loadingForm = null;
    }

loadingForm 在“繁重”工作结束时为 NULL - 因此永远不会调用 .Dispose()

线程和任务有什么区别?

为什么我的静态变量在第一种情况下保持全局,而在第二种情况下对于 Taks 的线程来说似乎是本地的?

【问题讨论】:

  • 你几乎违反了书中的每一条规则。该任务只会使情况变得更糟,它不是 STA。仅当您 understand how to do this 时才这样做。以正确的方式执行此操作,“繁重的工作”必须在工作线程上完成,始终保持 UI 线程可用于显示和更新 UI。
  • 大部分方法 witt Thread 都是从 Telerik sample 复制而来的
  • 启动画面通常是很无辜的,它们只是显示一个位图并且不使用使用 SystemEvents 类的控件。喜欢标签。随意省略关闭窗口的 Invoke() 调用也是一个重大错误。像这样的线程错误非常难以调试。不要这样做。
  • 好吧,让我们想象一下我的 FormWithLabel 里面只有一个图片框。我的问题中描述的行为有什么变化?线程仍然可以正常工作,但任务 - 不是......而且顺便说一句 - 你在我上面的代码示例中哪里看到 Invoke() 调用?
  • 更好的方法是从主 UI 线程(无任务或线程)创建您的 FormWithLabel,然后使用任务或线程来完成繁重的工作,并且可以使用 Invoke 从你的繁重工作。

标签: c# multithreading winforms task


【解决方案1】:

loadingForm 在 Dispose 时为 null 的问题可能是由于变量未标记为 volatile,但它被更多线程使用。在这种情况下,编译器或运行时可能会决定对变量进行优化(缓存)。

【讨论】:

  • loadingForm 静态变量是从任务中使用的,而不是从你的主线程中使用的,也就是说,你创建并启动了任务并且你处理了loadingForm。或者我只是不明白你的代码片段,所以你可以改进它吗?
  • 我的意思是 loadingForm 不用于您描述的两个线程(主 UI 线程和任务线程)
  • 因此您从多个线程中使用它,这导致了您的问题。这也与您的发现一致,即如果您将其更改为属性行为更改,因为它取决于复杂的优化。如前所述,考虑重新设计您的代码。
【解决方案2】:

所以我按照 Honza 的建议(从主 UI 线程创建 FormWithLabel)重新设计了我的代码,如下所示:

public MainForm()
    {
        InitializeComponent();
        FormWithLabel splash = new FormWithLabel();
        //start long-term work in a separate thread
        new Task(() => { DoHeavyWork(splash); }).Start();
        //FormWithLabel will be closed at the end of DoHeavyWork task
        splash.ShowDialog(this);
    }

private void DoHeavyWork(FormWthLabel splash)
    {
        for (int i = 1; i <= 5; i++)
        {
            //checking whether FormWithHelp handle is already created to ensure 
            //that following Invoke will not throw InvalidOperationException
            while (!lScreen.IsHandleCreated)
                Thread.Sleep(50);
            //changing text inside a label on FormWithLabel (assuming label is public)
            splash.Invoke(new Action(() => { splash.label.Text = "Loading item №" + i; }));   
            //some heavy work emulation
            Thread.Sleep(1000);                             
        }
        //closing FormWithLabel and continuing main thread (showing fully filled main form)
        splash.Invoke(new Action(splash.Close));
    }

【讨论】:

    【解决方案3】:

    当我们不希望应用程序线程由于密集的任务而滞后或变慢时使用任务。 就像在 javafx 应用程序中一样,我们不能直接使用线程,如果我们想让 ui 没有延迟和变慢,我们必须使用任务。如果要在线程执行后完成任何任务,则必须使用诸如 taskOnComplete 或 OnSucced 之类的侦听器。 这就是 dispose 没有被调用的原因

    对于多个并行任务,您可以使用 Executor 服务。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2015-06-12
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-05-12
      相关资源
      最近更新 更多