UI线程上的Dispatcher.Dispatch

时间:2012-04-02 16:23:38

标签: c# wpf

我怀疑何时使用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。

所以我的问题是,当我们必须这样做时,为什么在这种情况下它工作正常。

谢谢&amp;问候, BHavik

2 个答案:

答案 0 :(得分:7)

  

我怀疑何时使用Dispatcher.Invoke进行更新   来自不同线程的UI上的东西。

当您使用其他线程时,您将始终必须使用调度程序更新属于另一个线程的ui组件。

  

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

任务允许执行多个操作而不阻止调用它们的线程,但这并不意味着它们位于不同的线程上。但是,从任务内部更新UI时,您需要使用调度程序。

  

在这种情况下,它正在更新UI,但我已经看到了一些场景   人们使用Dispatcher.Invoke或BeginInvoke更新UI   不同的主题。

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

这是来自msdn文档,

  

在WPF中,只有创建DispatcherObject的线程才能访问   那个对象。例如,从中分离出的后台线程   主UI线程无法更新Button的内容   在UI线程上创建。为了后台线程访问   Button的Content属性,后台线程必须   将工作委托给与UI线程关联的Dispatcher。   这是通过使用Invoke或BeginInvoke来完成的。调用是   synchronous和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 });
        }));
    }
}

答案 1 :(得分:6)

您的测试无效,因为它实际上并未更新您的用户界面。如果您需要证明,请添加此睡眠呼叫:

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

您会发现您的用户界面显示且列表为空。 10秒,30秒,3个月后,列表将不包含您的字符串。

相反,您的测试证明了竞争条件 - 您的Test()方法的完成速度足够快,以便在 UI出现在屏幕上并读取列表之前将字符串添加到列表中。

要解决此问题,请将您的收藏集更改为ObservableCollection<string>。但是接下来你会遇到下一个问题 - 你无法在后台线程上更新ObservableCollection。这就是Dispatcher进来的地方。