【问题标题】:Joining a thread stops execution with no exception加入一个线程无一例外地停止执行
【发布时间】:2014-02-12 10:16:11
【问题描述】:

我的应用程序有 2 个线程 - 主 UI 线程和另一个线程。

应用程序在单独的线程上收集文件名列表。然后我希望这个列表显示在我的 GUI 中。该程序按需要运行,直到线程完成。它导致以下错误

"The calling thread must be STA, because many UI components require this."

运行以下代码,如您所见,它需要 2 个操作参数 - 一个用于 AddCurrentFile,一个用于完成。在duplication.GetDuplicateList() 期间,它调用了这两个方法。

        this._duplicatesThread = new Thread(() =>
        {
            try
            {
                duplication.GetDuplicateList(new Action<string>(AddCurrentFile), new Action<List<Duplicate>>(Complete));
                CreateParent();
            }
            catch (Exception ex)
            {
                string s = ex.ToString();
                throw;
            }
        });

AddCurrentFile 为我的属性提供一个值,并使用 INotifyProperty 我的 GUI 根据需要进行更新:

private void Complete(List<Duplicate> duplicateList)
{
   this._duplicateList = duplicateList;
}

问题是当我调用 CreateParent() 因为这会创建一个新的 UserControl - 然后我得到 The calling thread must be STA, because many UI components require this. 但这发生在 UserControl 的构造函数期间。以下代码是失败的地方

    private void CreateParent()
    {
        ObservableCollection<DuplicateControl> parent = new ObservableCollection<DuplicateControl>();

        foreach (var duplicate in this._duplicateList)
        {
            DuplicateControl ic = new DuplicateControl();//This fails as soon as I enter the constructor. The DuplicateControl uses the UserControl as a base class
            parent.Add(ic);
        }
        this.ParentDuplicate = parent;
    }

所以,为了安抚线程,我在 CreateParent() 方法中添加了this._duplicatesThread.Join;

但是,我认为(请正确/同意我的观点)此时发生的事情是因为我正在加入线程,它实际上取消了非 UI 线程,因此在它的进程的那个点停止!因此,它永远不会执行this._duplicatesThread.Join; 之后的行

现在,我希望 Programmers 最好不要问这个问题,但是,我觉得这里的问题更多是我的设计而不是代码(尽管我很高兴知道模式和我的代码很垃圾,呵呵)。

我的问题是,由于我无法通过新线程执行此操作(因为它不是 UI 线程),我需要做什么?就好像我需要像NewThread.FinishedExecuting += DoThisThing()这样的代表

【问题讨论】:

  • 听起来就像您只需要从其他线程的 UI 元素上调用 .Invoke()。您不应从与创建它的线程分开的线程访问 UI 组件。也就是说,您始终可以使用大多数 UI 组件都有的 .Invoke() 来编组对 UI 线程的调用。
  • 我很抱歉@sircodesalot,你是对的 - 我的代码有错误,我已经更新了。在构造函数中抛出异常!所以,在这个阶段,它只是创建一个对象,而不是将对象“分配”给控件。
  • 也就是说,即使您在一个单独的线程上创建一个用完单个单元的组件(根据 WPF 的要求),您也永远无法将该组件与您的其余部分连接起来UI,因为一旦 WPF 尝试从原始 UI 线程更新它,它就会由于上述原因而失败。简而言之,你应该使用一个 UI 线程,然后通过.Invoke() 对 UI 线程的调用进行编组。
  • 这样想,当一个控件被创建时,它会记录哪个线程创建了它,然后每次访问该控件时,它都会进行验证检查以确保它仅被该控件访问同一个线程。

标签: c# multithreading


【解决方案1】:

您应该将 ViewModel 与 View (UI) 分开,并使用 WPF 数据绑定将它们绑定在一起。实际上可以从后台工作线程更新 ViewModel 对象,WPF 框架将为数据绑定 UI 控件自动编组 INotifyPropertyChanged.PropertyChanged 通知(至少在 .NET 4.5 中,AFAIK)。这样,控件将根据需要在主 UI 线程上自动更新。

但是,我宁愿将 ViewModel 视为 UI 元素,即手动将所有 ViewModel 更新从后台线程编组到主 UI 线程。这可以通过Dispatcher.Invoke(同步)或Dispatcher.InvokeAsync(异步,推荐)来完成。

也可以使用Progress&lt;T&gt; 模式将更新传播到 UI 线程。这是一个相关的问题/答案:

How to execute task in the wpf background while able to provide report and allow cancellation?

【讨论】:

  • 啊,后台工作线程做了所有事情,就像那样 - 它甚至拥有我所希望的完整委托!!
【解决方案2】:
Thread thread = new Thread(MethodWhichCallesTheConstructor);
        thread.SetApartmentState(ApartmentState.STA); //Set the thread to STA
thread.Start(); 
thread.Join(); //Wait for the thread to end

【讨论】:

  • 这很酷,但现在你有两个问题。如果你在这个线程上创建一个控件,你如何将它附加到主 UI 线程上的 UI 的其余部分?
  • 你仍然可以从主 ui 线程指向这个对象,但基本上整个 ui 需要在 sta 上运行。在主 ui 类上方添加 [STAThread]
猜你喜欢
  • 2011-05-10
  • 1970-01-01
  • 2020-10-04
  • 2021-05-21
  • 1970-01-01
  • 2021-12-03
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多