Dispatcher.UI线程上的Dispatch

本文关键字:Dispatch 线程 UI Dispatcher | 更新日期: 2023-09-27 18:29:42

我对何时使用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>

我正在不同的线程上启动一个新任务,是否需要使用Dispatcher.BeginInvoke来更新UI。

在这种情况下,它是在更新UI,但我看到过一些场景,人们使用Dispatcher.Invoke或BeginInvoke从不同的线程更新UI。

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

谢谢&当做BHavik

Dispatcher.UI线程上的Dispatch

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

当您在不同的线程上时,您将始终必须使用调度器来更新属于另一个线程的ui组件。

我正在不同的线程上启动一个新任务,我需要使用吗Dispatcher.BeginInvoke以更新UI。

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

在这种情况下,它正在更新UI,但我看到了一些场景用户使用Dispatcher.Invoke或BeginInvoke从不同的线程。

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

这来自msdn文档,

在WPF中,只有创建DispatcherObject的线程才能访问那个物体。例如,从主UI线程无法更新在UI线程上创建。为了让后台线程访问按钮的Content属性,后台线程必须将工作委托给与UI线程关联的Dispatcher。这是通过使用Invoke或BeginInvoke来完成的。Invoke是synchronous和BeginInvoke是异步的。

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

当从任务中调用Test()时(不使用调度器),我得到了这样的错误:"调用线程无法访问此对象,因为另一个线程拥有它。"

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

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

因此,这里有一个工作示例,它将从任务中更新网格。


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 });
        }));
    }
}

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

public void Test()
{
    Thread.Sleep(10000);
    listOfString.Add("abc");
    listOfString.Add("abc");
    listOfString.Add("abc");
}

你会发现你的用户界面出现了,而列表是空的。10秒,30秒,3个月后,列表将不包含您的字符串。

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

若要修复此问题,请将集合更改为ObservableCollection<string>。但接下来你会遇到下一个问题——你不能在后台线程上更新ObservableCollection。这就是Dispatcher的作用所在。