为了让我的问题变得简单,我可以说我使用线程来快速更新字符串中的替换。在我的真实代码中,我需要一个线程。我知道在这个简单的例子中我可以避免它。
所以,我的软件有两个字段。用户选择一个文件,写一个(某种)正则表达式,并在他输入句子的同时查看修改的结果。我在用户选择文件时启动线程(请参阅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中执行此操作是否是执行此操作的最佳方法。调度员的实用性和良好实践?
答案 0 :(得分:4)
我认为你的单一帖子中有很多问题,我会尽力解决这些问题:
从其他主题访问可视控件
Winforms和WPF都是基于只有一个线程可以更改对象状态的事实构建的,当然该线程与创建对象的线程相同。
你可以想象为什么这很重要:控件是知道如何“渲染”或“绘制”自己的对象。在绘制,重新调整大小,移动/拖动时,对象的属性不能从控件本身的“外部”更改。当然,主线程已经忙于进行我提到的转换,因此保证在主线程上运行的“用户”代码不会改变它。但是,另一个并行运行的线程可能就是这样。例如,想象一下,主线程正在从TextBox
呈现文本,并且当第二个线程更改文本时,写入了一半的单词。例如,这会导致计算文本宽度的问题。
使用调度程序
线程调度程序所做的是*编组*代码到主线程。你看,包括WinForms和WPF在内的大多数可视化框架都基于“应用程序循环”。这意味着您的应用程序在while(true){}
块内运行。您的代码(例如,listViewFiles_SelectionChanged
)在需要时从此循环中调用。管理此调用的对象是Dispatcher
。它有一个队列要运行的东西,它决定下次运行。当调度程序调用的代码运行时,应用程序的可视部分没有其他任何事情发生 - 毕竟,这就是线程正在做的事情,对吧?因此它无法处理用户输入,重新渲染屏幕等。
Dispatcher
为您提供了一个接口,可以从另一个线程调用发布调度程序调用的新方法,方法是将其插入队列。它不是立即执行的,你可以理解:你在调用main的第二个线程上,main可能忙于呈现屏幕,处理输入甚至运行代码,例如listViewFiles_SelectionChanged
。一旦循环迭代结束,Dispatcher
将检查队列。根据您的方法的优先级,它可能是下一个要执行的甚至等待几次迭代。
出于这个原因,如果你在调度程序方法的第二个线程中粘贴你正在做的所有事情,你实际上是要求框架在主线程上运行你的第二个线程代码。并且,由于您的线程代码永远运行,主线程将永远忙于运行该代码(DoWork
),并且将无法再执行任何其他操作。
调度员良好做法
因此,在上述项目的结论中,当您将代码编组到调度程序时,主线程会忙于执行此操作。忙碌时,您的应用程序变得无响应。由于我们一直想要一个响应式应用程序,我们必须在主线程上执行尽可能少的,也就是说,无论我们要求Dispatcher进行编组。
在这种情况下,你必须做的只是访问控制的元帅行。即使这意味着调用Dispatcher的许多次调用 - 当然,你会为这些调用付出代价,但这比使用你的代码的主线程超过必要的时间更好。
使用具有单线程的调度程序
即使您没有第二个帖子,Dispatcher
也可以帮助您。如果需要执行长计算,可以使用一组标志或enum
来跟踪状态。在该方法中,您调用调度程序传递您自己的方法(具有低优先级)并返回它们(因此您在部分中断计算)。
答案 1 :(得分:1)
问题是,您的代码正在访问_searchTextBox.Text和__replaceTextBox.Text。 您的调度程序解决方案正在运行,但实际上并没有解决任何问题。代码将在UI线程中执行,但在ListView选择更改后不会立即执行。
要使其工作,请返回到没有调度程序的第一个版本,但将SearchText和ReplaceText作为线程启动参数传递。 这是一些伪代码:
var searchText = _searchTextBox.Text;
var replaceText = _replaceTextBox.Text
Thread.Start(() => DoWork(searchText, replaceText));
答案 2 :(得分:0)
您只能在创建控件的线程中访问控件。在大多数情况下,它是UI线程。 Windows Forms就是这种情况,WPF也是如此。调用Dispatch只是阻塞你的线程并在UI线程中运行其余部分,这消除了将工作卸载到其他线程的大部分好处,当然也阻止了UI线程做其他工作。正如上面提到的那样,解决方案是将UI代码与后台工作程序代码分开。在这种情况下,它很简单,因为您只使用接口中的输入值,您可以从UI线程中添加项目的队列中获取输入值。需要同步访问队列以避免竞争条件(lock(queue.Synch))。这是Produce-Consumer设计模式的经典案例。
答案 3 :(得分:0)
所以,如果我们只看你的代码,我建议将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&amp; there是有关调度员/线程的有趣问题
P.S。:我一般都没有检查你的多线程代码,因为如果你在这里使用mvvm /其他模式可以大大改善它