C# 在UI线程上调度

C# 在UI线程上调度,c#,wpf,C#,Wpf,我对何时使用Dispatcher.Invoke从不同的线程更新UI上的内容有疑问 这是我的密码 public Window4() { InitializeComponent(); this.DataContext = this; Task.Factory.StartNew(() => Test() ); } private List<string> listOfString = new List<st

我对何时使用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>
公共窗口4()
{
初始化组件();
this.DataContext=this;
Task.Factory.StartNew(()=>Test());
}
private List listOfString=new List();
公共列表列表字符串
{
获取{return listOfString;}
设置{listOfString=value;}
}
公开无效测试()
{
添加(“abc”);
添加(“abc”);
添加(“abc”);
}
我正在不同线程上启动新任务,是否需要使用Dispatcher.BeginInvoke更新UI

在本例中,它正在更新UI,但我看到过一些场景,其中人们使用Dispatcher.Invoke或BeginInvoke从不同的线程更新UI

所以我的问题是什么时候我们必须这样做,为什么在这种情况下它工作得很好

谢谢和问候,
BHavik

您的测试无效,因为它实际上没有更新您的UI。如果需要证据,请添加此睡眠呼叫:

public void Test()
{
    Thread.Sleep(10000);
    listOfString.Add("abc");
    listOfString.Add("abc");
    listOfString.Add("abc");
}
你会发现你的UI出现了,列表是空的。10秒,30秒,3个月后,列表将不包含您的字符串

相反,您的测试正在演示一个竞争条件—您的test()方法完成得足够快,以至于在UI出现在屏幕上并读取列表之前,字符串被添加到列表中

要修复此问题,请将集合更改为
可观察集合
。但是接下来您将遇到下一个问题-您无法在后台线程上更新
ObservableCollection
。这就是
调度器的作用

我对何时使用Dispatcher.Invoke更新有疑问 UI上的某些内容来自不同的线程

当您在另一个线程上时,您将始终必须使用dispatcher更新属于另一个线程的ui组件

我正在另一个线程上启动一个新任务,是否需要使用 Dispatcher.BeginInvoke以更新UI

任务允许在不阻塞调用它们的线程的情况下执行多个操作,但这并不意味着它们位于不同的线程上。但是,从任务内部更新UI时,需要使用dispatcher

在本例中,它正在更新UI,但我看到一些场景 人们使用Dispatcher更新UI。从 不同的线程

Invoke将在调用线程执行操作时阻止该线程,而BeginInvoke不会。BeginInvoke将立即将控制权返回给调用方,如果调用线程正在执行繁重的操作,则Invoke可能会导致调用线程挂起

这来自msdn文档

在WPF中,只有创建DispatcherObject的线程才能访问 那个物体。例如,从中剥离的背景线程 主UI线程无法更新已删除的按钮的内容 在UI线程上创建。以便后台线程访问 按钮的内容属性,背景线程必须 将工作委托给与UI线程关联的调度程序。 这是通过使用Invoke或BeginInvoke来实现的。调用是 同步和BeginInvoke是异步的

编辑:为了回应你的评论,我做了一些测试

当从任务调用Test()时(不使用dispatcher),我得到了这样一个错误:“调用线程无法访问此对象,因为它属于另一个线程。”

所以我创建了一个名为PrintThreadID()的方法。我在进入任务之前打印了线程,然后从任务内部打印,它确实报告两个线程都在同一个线程上运行

这个错误是误导性的,因为它说调用线程与拥有它的线程不同,PrintThreadID()函数显示的不是真的,它们实际上在同一个线程上。在同一线程上的任务如果不使用Dispather.Invoke()仍无法更新UI组件

下面是一个工作示例,它将从任务更新网格


公共部分类主窗口:窗口
{
公共列表myList{get;private set;}
公共主窗口()
{
初始化组件();
myList=新列表();
label1.Content=Thread.CurrentThread.ManagedThreadId.ToString();
Task.Factory.StartNew(PrintThreadID);
任务.工厂.开始新(测试);
}
私有void PrintThreadID()
{
label1.Dispatcher.Invoke(新操作(()=>
label1.Content+=“…”+Thread.CurrentThread.ManagedThreadId.ToString());
}
专用无效测试()
{
myList.Add(“abc”);
myList.Add(“abc”);
myList.Add(“abc”);
//如果不使用dispatcher,则会出现错误“调用线程无法访问此对象,因为其他线程拥有它。”
dataGrid1.Dispatcher.Invoke(新操作(()=>
{
dataGrid1.ItemsSource=myList.Select(i=>new{Item=i});
}));
}
}

但如果我正在更新或添加绑定到UI的列表,是否仍需要使用Dispatcher的Invoke或BeginInvoke…取决于列表类型<代码>列表
:否,但无论如何更新都不会显示在UI中
ObservableCollection
:是的。当从任务内部更新它时,是的,您必须调用Dispatcher.Invoke(),我在上面的回答中发布了测试的完整示例来说明这一点。我不太理解您的回答。UI线程将简单地休眠10秒,并添加“abc”项。
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 });
        }));
    }
}