Dispatcher.Dispatch в потоке пользовательского интерфейса - PullRequest
4 голосов
/ 02 апреля 2012

У меня есть сомнения относительно того, когда использовать Dispatcher.Invoke для обновления чего-либо в пользовательском интерфейсе из другого потока.

Вот мой код ...

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 для обновления пользовательского интерфейса.

В этом случае это обновление пользовательского интерфейса, но я видел несколько сценариев, когда люди обновляют пользовательский интерфейс, используя Dispatcher.Invoke или BeginInvoke из другого потока.

Итак, мой вопрос, когда мы должны это делать и почему в этом случае все работает нормально.

Спасибо и С уважением, BHavik

Ответы [ 2 ]

7 голосов
/ 02 апреля 2012

У меня есть сомнения относительно того, когда использовать Dispatcher.Invoke для обновления. что-то на UI из другой темы.

Когда вы находитесь в другом потоке, вам всегда придется использовать диспетчер для обновления компонента пользовательского интерфейса, принадлежащего другому потоку.

Я запускаю новую задачу в другой теме, нужно ли мне использовать Dispatcher.BeginInvoke для обновления пользовательского интерфейса.

Задачи позволяют выполнять несколько операций без блокировки потока, из которого они вызваны, но это не означает, что они находятся в другом потоке. Однако при обновлении пользовательского интерфейса из Задачи вам необходимо использовать диспетчер.

В данном случае это обновление пользовательского интерфейса, но я видел несколько сценариев, в которых люди обновляют пользовательский интерфейс с помощью Dispatcher.Invoke или BeginInvoke из другая тема.

Invoke заблокирует вызывающий поток, пока он выполняет действие, а BeginInvoke - нет. BeginInvoke немедленно вернет управление вызывающей стороне, Invoke может вызвать зависание вызывающего потока, если он выполняет тяжелую операцию.

Это из документации MSDN,

В WPF только поток, создавший DispatcherObject, может получить доступ этот объект. Например, фоновый поток, который выделяется из основной поток пользовательского интерфейса не может обновить содержимое кнопки, которая была создан в потоке пользовательского интерфейса. Для доступа к фоновому потоку свойство Content кнопки, фоновый поток должен делегировать работу диспетчеру, связанному с потоком пользовательского интерфейса. Это достигается с помощью Invoke или BeginInvoke. Вызвать синхронный и BeginInvoke является асинхронным.

Редактировать: В ответ на ваш комментарий я провел несколько тестов.

При вызове Test () из задачи (без использования диспетчера) я получаю эту ошибку: «Вызывающий поток не может получить доступ к этому объекту, поскольку он принадлежит другому потоку».

Итак, я создал метод с именем PrintThreadID (). Я распечатал поток перед тем, как войти в задачу, а затем изнутри задачи, и она сообщает, что обе они работают с идентичным потоком .

Ошибка вводит в заблуждение, потому что говорит, что вызывающий поток отличается от того, которому принадлежит его, который, как показывает функция PrintThreadID (), не соответствует действительности, на самом деле он находится в одном потоке. Задачи, находящиеся в том же потоке, все еще не могут обновить компонент пользовательского интерфейса без использования Dispather.Invoke ().

Итак, вот рабочий пример, который обновит 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 });
        }));
    }
}
6 голосов
/ 02 апреля 2012

Ваш тест недействителен, поскольку на самом деле не обновляет ваш пользовательский интерфейс. Если вам нужны доказательства, добавьте этот спящий звонок:

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

Вы обнаружите, что появляется ваш пользовательский интерфейс и список пуст. Через 10, 30, 3 месяца в списке не будет ваших строк.

Вместо этого ваш тест демонстрирует состояние гонки - ваш метод Test () завершается достаточно быстро, чтобы строки добавлялись в список до , пользовательский интерфейс появляется на экране и читает список.

Чтобы исправить это, измените свою коллекцию на ObservableCollection<string>. Но тогда вы столкнетесь со следующей проблемой - вы не можете обновить ObservableCollection в фоновом потоке. Вот где приходит Dispatcher.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...