我怀疑何时使用Dispatcher.Invoke从不同的线程更新UI上的内容.
这是我的代码......
public Window4()
{
InitializeComponent();
this.DataContext = this;
Task.Factory.StartNew(() => Test() );
}
private List<string> listOfString = new List<string>();
public List<string> ListOfString
{
get { return listOfString; }
set { listOfString = value; }
}
public void Test()
{
listOfString.Add("abc");
listOfString.Add("abc");
listOfString.Add("abc");
}
<Grid>
<ListView ItemsSource="{Binding ListOfString}" />
</Grid>
Run Code Online (Sandbox Code Playgroud)
我在不同的线程上启动一个新任务,我是否需要使用Dispatcher.BeginInvoke来更新UI.
在这种情况下,它正在更新UI,但我已经看到一些场景,人们使用来自不同线程的Dispatcher.Invoke或BeginInvoke更新UI.
所以我的问题是我们必须这样做,为什么在这种情况下它工作正常.
感谢和问候,BHavik
我怀疑何时使用Dispatcher.Invoke从不同的线程更新UI上的内容.
当您在不同的线程上时,您将始终必须使用调度程序来更新属于另一个线程的ui组件.
我在不同的线程上启动一个新任务,我是否需要使用Dispatcher.BeginInvoke来更新UI.
任务允许执行多个操作而不阻止调用它们的线程,但这并不意味着它们位于不同的线程上.但是,从任务内部更新UI时,您将需要使用调度程序.
在这种情况下,它正在更新UI,但我已经看到一些场景,人们使用来自不同线程的Dispatcher.Invoke或BeginInvoke更新UI.
调用将在执行操作时阻止调用线程,而BeginInvoke则不会.BeginInvoke会立即将控制权返回给调用者,Invoke可能会导致调用线程在执行繁重操作时挂起.
这是来自msdn文档,
在WPF中,只有创建DispatcherObject的线程才能访问该对象.例如,从主UI线程分离出来的后台线程无法更新在UI线程上创建的Button的内容.为了让后台线程访问Button的Content属性,后台线程必须将工作委托给与UI线程关联的Dispatcher.这是通过使用Invoke或BeginInvoke来完成的.Invoke是同步的,BeginInvoke是异步的.
编辑:为了回应你的评论,我进行了一些测试.
从任务调用Test()时(不使用调度程序)我收到此错误"调用线程无法访问此对象,因为另一个线程拥有它."
所以我创建了一个名为PrintThreadID()的方法.我在输入任务之前打印了线程,然后从任务内部打印,它确实报告两个都在相同的线程 ID 上运行.
该错误是误导性的,因为它说调用线程与拥有它的那个不同,PrintThreadID()函数显示的不是真的,它们实际上在同一个线程上.在不使用Dispather.Invoke()的情况下,在同一线程上的任务仍然无法更新UI组件.
所以这是一个工作示例,它将从任务更新Grid.
public partial class MainWindow : Window
{
public List<string> myList { get; private set; }
public MainWindow()
{
InitializeComponent();
myList = new List<string>();
label1.Content = Thread.CurrentThread.ManagedThreadId.ToString();
Task.Factory.StartNew(PrintThreadID);
Task.Factory.StartNew(Test);
}
private void PrintThreadID()
{
label1.Dispatcher.Invoke(new Action(() =>
label1.Content += "..." + Thread.CurrentThread.ManagedThreadId.ToString()));
}
private void Test()
{
myList.Add("abc");
myList.Add("abc");
myList.Add("abc");
// if you do not use the dispatcher you will get the error "The calling thread cannot access this object because a different thread owns it."
dataGrid1.Dispatcher.Invoke(new Action(() =>
{
dataGrid1.ItemsSource = myList.Select(i => new { Item = i });
}));
}
}
Run Code Online (Sandbox Code Playgroud)
您的测试无效,因为它实际上并未更新您的UI.如果您需要证明,请添加此睡眠呼叫:
public void Test()
{
Thread.Sleep(10000);
listOfString.Add("abc");
listOfString.Add("abc");
listOfString.Add("abc");
}
Run Code Online (Sandbox Code Playgroud)
您会发现您的用户界面显示且列表为空.10秒,30秒,3个月后,列表将不包含您的字符串.
相反,您的测试正在演示竞争条件 - 您的Test()方法的完成速度足够快,以便在UI出现在屏幕上并读取列表之前将字符串添加到列表中.
要修复它,请将您的收藏更改为ObservableCollection<string>.但是接下来你会遇到下一个问题 - 你无法ObservableCollection在后台线程上更新.这就是Dispatcher进来的地方.