【问题标题】:Utility and good practice of the DispatcherDispatcher 的实用性和良好实践
【发布时间】:2012-05-18 14:20:08
【问题描述】:

为了让我的问题简单,让我们说我使用一个线程来动态更新字符串中的替换。在我的真实代码中,我需要一个线程。我知道我可以在这个简单的例子中避免它。

所以,我的软件有两个字段。用户选择一个文件,编写一个(某种)正则表达式并在输入句子的同时查看修改结果。我在用户选择文件时启动线程(请参阅 listViewFiles_SelectionChanged 方法)。我的线程的工作是在 DoWork 方法中。

public void DoWork()
    {
        while (true)
        {
            FileData fileData = _selectedFile;
            if (fileData != null)
            {
                string name = fileData.FileName;
                string searchRegEx = GenerateRegex(_searchTextBox.Text);
                string replacement = _replaceTextBox.Text;
                name = Regex.Replace(name, searchRegEx, replacement);
                /*
                foreach (var action in _actionCollection)
                {
                    name = action.Rename(name);
                }*/

                _searchSample.Content = fileData.FileName;
                _replaceSample.Content = name;
            }
            Thread.Sleep(1000);
        }
    }

    private void listViewFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _selectedFile = listViewFiles.SelectedItem as FileData;
        _thread.Start();
    }

当我的线程执行他的工作时,我在行字符串 searchRegEx = GenerateRegex(_searchTextBox.Text); 上遇到异常。 :调用线程无法访问此对象,因为不同的线程拥有它。我读了很多关于这个异常的内容,但我不明白。

为了解决这个问题,我用 Dispatcher 包围了我的代码。我不明白机制,但它的工作原理。我不知道它是否正确或高效,但它有效。

 public void DoWork()
    {
        while (true)
        {
            FileData fileData = _selectedFile;
            if (fileData != null)
            {
                //use Window.Dispatcher
                this.Dispatcher.Invoke(System.Windows.Threading.DispatcherPriority.Normal,
                    new Action(delegate()
                    {
                        string name = fileData.FileName;
                        string searchRegEx = GenerateRegex(_searchTextBox.Text);
                        string replacement = _replaceTextBox.Text;
                        name = Regex.Replace(name, searchRegEx, replacement);
                        /*
                        foreach (var action in _actionCollection)
                        {
                            name = action.Rename(name);
                        }*/

                        _searchSample.Content = fileData.FileName;
                        _replaceSample.Content = name;
                    }));
            }
            Thread.Sleep(1000);
        }
    }

    private void listViewFiles_SelectionChanged(object sender, SelectionChangedEventArgs e)
    {
        _selectedFile = listViewFiles.SelectedItem as FileData;
        _thread.Start();
    }

我想知道这段代码是否正确和正确。您会在评论中看到 foreach 指令。我的代码应该做很多工作,我不知道在 delagate 中这样做是否是最好的方法。 Dispatcher 的实用性和良好实践?

【问题讨论】:

  • 为什么你根本不使用 mvvm ? Dispatcher.Invoke 只是在 UI 线程中同步调用委托,因此此代码可能会导致 ui 冻结。由于尝试在不同的线程中访问 _searchTextBox 元素而出现错误,因此您只需将 ui 更新和代码分开即可。
  • @trimeyko 因为我对 MVVM 不太满意。但你说得对,我应该使用它。
  • 您是否考虑过使用 BlockingQueue 作为快速修复?从 UI 推送 _replaceTextBox.Text 并在另一个线程中执行工作。
  • @trimeyko,我不明白为什么 MVVM 会解决这个问题。顺便说一句,绑定更难线程化,因为您很难诊断问题。

标签: c# .net multithreading


【解决方案1】:

我认为您的单个帖子中有很多问题,我会尽力解决所有问题:

从其他线程访问可视化控件

Winforms 和 WPF 都是基于以下事实构建的:只有单个线程可以更改对象状态,当然,该线程与创建对象的线程相同。

您可以想象为什么这很重要:控件是知道如何“渲染”或“绘制”自身的对象。在绘制、调整大小、移动/被拖动时,不能从控件本身的“外部”更改对象的属性。当然,主线程已经忙于进行我提到的转换,所以它保证在主线程上运行的“用户”代码不会改变它。但是,另一个并行运行的线程可能正是这样做的。例如,想象一下,主线程正在渲染来自TextBox 的文本,并且当第二个线程更改文本时写入了一半的单词。例如,这会导致计算文本宽度出现问题。

使用调度程序

线程调度程序所做的是将您的代码编组到主线程。你看,大多数可视化框架,包括 WinForms 和 WPF,都是基于“应用程序循环”的。这意味着您的应用程序在 while(true){} 块内运行。您的代码(例如,listViewFiles_SelectionChanged)会在需要时从此循环中调用。管理此调用的对象是Dispatcher。它有一个 queue 要运行的东西,它决定接下来运行。当调度程序调用的代码运行时,应用程序的可视部分不会发生任何其他事情——毕竟,这就是线程正在做的事情,对吧?所以它不能处理用户输入、重新渲染屏幕等。

Dispatcher 为您提供了一个可以从另一个线程调用的接口,该线程发布一个由调度程序调用的新方法,方法是将其插入队列中。正如您所理解的,它不会立即执行:您在调用主线程的第二个线程上,主线程可能正忙于渲染屏幕、处理输入甚至运行您的代码,例如listViewFiles_SelectionChanged。一旦循环迭代结束,Dispatcher 将检查队列。根据您的方法的优先级,它可能是下一个要执行的方法,或者甚至等待更多的迭代。

因此,如果您将您正在执行的所有操作都放在调度程序方法的第二个线程中,那么您实际上是在要求框架在主线程上运行您的第二个线程代码。而且,由于您的线程代码永远运行,主线程将永远忙于运行该代码 (DoWork),并且无法再做任何其他事情。

调度员良好做法

因此,综上所述,当您将代码编组到调度程序时,主线程会忙于执行此操作。在忙碌时,您的应用程序变得无响应。因为我们一直想要一个响应式应用程序,所以我们必须在主线程上尽可能少地,也就是说,在我们要求 Dispatcher 编组的任何事情上。

既然如此,你必须做的只是编组访问控件的。即使这意味着对 Dispatcher 的多次调用——当然,你会为这些调用付出代价,但总比在主线程中停留的代码超出必要的时间要好。

在单线程中使用调度程序

即使您没有第二个线程,Dispatcher 也可以为您提供帮助。如果您需要执行长计算,您可以使用一组标志或enum 来跟踪状态。在该方法中,您调用调度程序传递您自己的方法(具有低优先级)并且它们返回(因此您将计算部分中断)。

【讨论】:

    【解决方案2】:

    问题是,您的代码正在访问 _searchTextBox.Text 和 __replaceTextBox.Text。 您使用调度程序的解决方案正在运行,但实际上并没有解决任何问题。代码将在 UI 线程中执行,但不会在 ListView 选择更改后立即执行。

    要使其正常工作,请在没有调度程序的情况下返回您的第一个版本,但将 SearchText 和 ReplaceText 作为线程启动参数传递。 这是一些伪代码:

    var searchText = _searchTextBox.Text;
    var replaceText = _replaceTextBox.Text
    Thread.Start(() => DoWork(searchText, replaceText));
    

    【讨论】:

      【解决方案3】:

      您只能在创建 的线程中访问您的控件。在大多数情况下,它是 UI 线程。 Windows 窗体就是这种情况,WPF 就是这种情况。调用 Dispatch 只会阻塞您的线程并在 UI 线程中运行其余线程,这会带走将工作卸载到其他线程的大部分好处,当然还会阻止 UI 线程执行其他工作。如上所述,解决方案是将您的 UI 代码与后台工作人员代码分开。在这种情况下,它很简单,因为您只使用来自界面的输入值,您可以从您在 UI 线程中添加项目的队列中获取这些值。需要同步对队列的访问以避免竞争条件(lock(queue.Synch))。这是 Produce-Consumer 设计模式的经典案例。

      【讨论】:

        【解决方案4】:

        所以,如果我们只查看您的代码,我建议仅将 ui update/get 代码包装到调用中,因为正如我所说,目前您只是在 UI 线程中调用您的代码,而您的附加线程无用,因为它只是调用和睡觉。

        这是一个例子:

         private Thread _thread;
        
            public MainWindow()
            {
                InitializeComponent();
        
                _thread = new Thread(DoWork);
        
                _thread.Start();
            }
        
            private void DoWork()
            {
                while (true)
                {
                    var str = (string)Dispatcher.Invoke(new Func<object>(() => NotifyLabel.Content));
        
                    str += "a";
        
                    Dispatcher.Invoke(new Action(() => NotifyLabel.Content = str));
        
                    Thread.Sleep(500);
                }
            }
        

        我建议您阅读 invoke info 和 there 是关于调度程序/线程的有趣问题

        P.S.:我一般不会审查你的多线程代码,因为如果你在这里使用 mvvm/其他模式可以大大改进它

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2017-02-27
          • 2017-10-12
          • 2017-10-27
          相关资源
          最近更新 更多